简介:MapCutter 3.13.0(旧名MapTiler)是一款面向GIS开发者和WebGIS前端工程师的地图金字塔切图工具,覆盖百度、高德、腾讯、天地图、谷歌、必应、MapBox等主流地图源,用于解决自定义地图、图片叠加层和瓦片金字塔图的高效切图与精准定位问题,支持leaflet、maptalks、openlayers、cesium及自定义模版输出。压缩包共302个文件、152.5MB,以dll运行库、pak资源、gfs配置、csv坐标与属性数据、json配置为主,并含itrf2008/itrf2014、nad27/nad83等多套坐标参考文件,可支撑不同投影与基准下的切图需求。目前已有286人学习。新版修正了百度地图偏差,支持25级切图、图层透明度调节、WebGL输出及OpenLayers/Cesium网页输出;内置谷歌内核小型网页开发环境,可直接调试修改生成的网页;提供图片旋转、缩放、去黑背景、增清等预处理和地图切块拼合工具,还支持根据图片坐标自动定位与多点调整区域、设置图层宽高匹配游戏像素,能有效提升瓦片生产与地图集成的效率。
1. 地图切图工具到底解决什么问题:从在线底图到离线金字塔
做 GIS 和 WebGIS 的人,几乎没有不被“地图切图”这件事绊过脚的。明明底图资源就在那,但项目要部署到内网、要控制首屏加载速度、要保证关掉外网也能用的时候,你就得把在线地图切成离线瓦片,按金字塔结构存成图片目录。MapCutter 3.13.0 就是干这个的:把百度、高德、腾讯、天地图、谷歌、必应、MapBox 这些常见源的地图数据,在本地按缩放级别直接切出标准金字塔瓦片,交给 Leaflet、OpenLayers 或桌面端离线地图直接挂载。它适合两类人:一类是天天被底图卡脖子的 WebGIS 集成工程师,另一类是刚开始做离线地图项目、想尽快把瓦片跑通的开发者。接下来的篇幅,我把从认知原理、参数配置到踩坑点的完整链路拆给你。
2. 切图前的认知准备:金字塔原理、坐标系与地图源选型
2.1 金字塔切片怎么组织:从经纬度到瓦片编号
瓦片金字塔本质上是一个递归四分的空间索引。缩放级别 z=0 时,整个世界被压成一张 256 x 256 的图片,文件名通常是 0/0/0.png;到 z=1 时,这张图被切成左上、右上、左下、右下四张,即 1/0/0.png、1/1/0.png、1/0/1.png、1/1/1.png。每增加一级,瓦片数量变成上一级的 4 倍,分辨率翻一番。这个结构的好处是:地图服务端和前端都不需要关心整张图有多大,前端只按当前视野范围去请求少量瓦片,加载速度就会非常快。
瓦片编号规则是 z/x/y.png,但这里的 y 方向从哪开始算,不同引擎有分歧。Web 墨卡托标准里,y 是从北到南递增,也就是左上角为原点,这套规则叫 XYZ;而 TMS 协议规定 y 从南到北递增,原点在左下角。如果你把一套 TMS 瓦片直接丢给 Leaflet 当 XYZ 用,地图会上下颠倒错位。MapCutter 这类工具在输出目录时通常提供两种命名模式,切完以后先确认你用的前端框架认的是哪一种。
想验证自己对瓦片编号的理解,可以在本地写个很短的脚本,把一个经纬度坐标换算成某级别下的瓦片坐标:
import math def deg_to_tile(lat: float, lng: float, zoom: int) -> tuple: lat_rad = math.radians(lat) n = 2.0 ** zoom x = (lng + 180.0) / 360.0 * n y = (1.0 - math.log(math.tan(lat_rad) + 1.0 / math.cos(lat_rad)) / math.pi) / 2.0 * n return int(x), int(y) # 某区域中心点,z=15 时对应的瓦片编号 print(deg_to_tile(30.5, 100.5, 15))这个函数把经纬度投影到 Web 墨卡托平面的单位网格里,再乘以当前级别的总瓦片数。x 的计算可以理解为经度占全球 360 度的比例;y 的计算涉及墨卡托投影的纬度拉伸,内部用 tan 和 cos 把纬度映射到投影平面。自己跑一遍这个脚本,后面遇到瓦片错层、偏移排查时,你会更容易判断是切图工具的问题,还是前端读取规则的问题。
2.2 地图源的坐标系差异:为什么统一投影是切图第一关
切图工具之所以要内置多个地图源,不只是因为这些源的底图风格不同,更深层的原因是它们用了不同的坐标系和投影。谷歌、必应、MapBox 走的是 Web 墨卡托 EPSG:3857,属于行业默认,全球通用;高德和腾讯用的是 GCJ02 加密坐标系,和 WGS84 的真实经纬度差几十到几百米;百度则更特殊,底图用的是 BD09MC 墨卡托投影,而它的经纬度接口是 BD09LL,坐标转换逻辑和高德、腾讯还不一样;天地图提供的切片虽然部分输出 EPSG:3857,但数据基准是 CGCS2000,叠加国外数据源时也需要注意坐标参考的一致性。
这意味着什么?你在 MapCutter 里切高德瓦片时,工具内部是把 GCJ02 范围换算到对应的投影平面再切片的。如果你同时想用百度和高德的瓦片做同一张地图的底图叠加,坐标基准不同,边缘会明显错位,这不是切图工具能简单修复的,除非你在切之前先做数据源的坐标统一。所以建任务前先问自己一个问题:这份瓦片最终要跟什么数据叠加?如果和 GPS 采集的轨迹叠加,优先选天地图或谷歌这种 WGS84/3857 体系;如果只是展示路网,高德和百度的风格更贴近国内用户,但后续接矢量图层时往往要做坐标转换。
2.3 地图源选型参考:不同源的切片组织差异
地图源的差异不只是投影,还体现在瓦片命名方式上。下面这张表是我在实际项目里整理过的,选源之前对照着看,能少走不少弯路。
| 地图源 | 投影基准 | 瓦片组织 | y 轴方向 | 适用场景 |
|---|---|---|---|---|
| 谷歌 | EPSG:3857 | z/x/y | 上南下北 | 国际项目、叠加 GPS 轨迹 |
| 必应 | EPSG:3857 | QuadKey | 无 x/y,编码串 | 少数前端框架专用 |
| 高德 | GCJ02 加密 | z/x/y | 上南下北 | 国内路网展示 |
| 腾讯 | GCJ02 加密 | z/x/y | 上南下北 | 国内城市项目 |
| 百度 | BD09MC | z/x/y | 上南下北 | 百度生态内使用 |
| 天地图 | CGCS2000 | z/x/y | 上南下北 | 政务、测绘合规 |
| MapBox | EPSG:3857 | z/x/y | 上南下北 | 自定义样式底图 |
选源还有一个容易被忽略的点:必应的 QuadKey 本质上是把 x/y 交错编码成一个字符串,如果前端加载库不支持 QuadKey,就得在服务端做一层转换。MapCutter 3.13.0 在输出必应瓦片时一般还是按 z/x/y 落盘,这样反而比源站的命名更好挂载。如果你在项目里必须用必应原站的 QuadKey 直接拉瓦片,则需要写一段小工具做 QuadKey 和 xy 的双向转换,不要指望切图目录里直接给你生成。
2.4 哪些场景不该用切图工具
切图不是万能的。栅格切片把一张底图变成上万个 png,好处是加载快、部署简单,坏处是改样式、改数据都得重切。如果你的项目还在频繁调风格,比如配图颜色一周改三次,不要急着切片,先用动态渲染服务顶着,等样式稳定了再考虑切图。还有一种情况,数据量本身很小,比如只是一个园区的小范围瓦片,数据更新周期又短,那直接用动态服务画矢量更好,切图的工程开销反而大于收益。
另外,矢量瓦片和栅格瓦片是两套完全不同的技术路线。MapCut的这类栅格切图工具解决的是“把图变成图片砖块”的问题;矢量瓦片保存的是几何数据和样式规则,由前端渲染,MapCutter 切不出来。如果你的项目需要做三维、需要前端交互拾取地物属性,得走 vector tiles 工具链,这里不做展开。总结成一句话:切图适合“图不常变、量又大、要离线跑”的底图场景,不适合“高频更新、强交互、样式未定型”的项目。
3. 把地图切成离线瓦片:MapCutter 操作全流程
3.1 拿到工具包先确认的三件事
MapCutter 3.13.0 是绿色免安装包,解压后目录里主要是主程序、地图源配置文件、输出目录模板和日志文件。第一次拿到手,别急着点开始切图,先做三件事。第一,确认运行环境,工具基于 .NET Framework 的话需要对应版本的运行库,缺失时程序会直接报错或者闪退;第二,确认磁盘剩余空间,这个后面会重点讲如何估算;第三,检查配置文件里的地图源 key,部分地图源要求填写开发者密钥或者配置代理出口,不填会切出一堆空瓦片。
打开主程序后,界面一般分成左侧任务列表、右侧参数区。先新建一个任务,让它走一遍“选源 → 定范围 → 定级别 → 输出”的流程。这里有个小习惯:我一般先只切到 z=5 的顶层瓦片,数量少、速度快,用来检查范围选得对不对。确认没问题了,再重跑完整级别,避免辛辛苦苦切了几万张发现范围偏了一个街区的低级错误。
3.2 新建切图任务:地图源、范围、级别与输出
在 MapCutter 里新建任务时,界面会弹出一个参数配置面板,本质上是生成一份任务描述文件。多数商业切图工具会把任务保存成 json 或 xml,我习惯直接编辑这份文件,因为它比一次次点界面更可控。下面是一个典型的任务配置,注释里写了每个字段的含义:
{ "source": "tianditu_vec", "bounds": [100.0, 30.0, 101.0, 31.0], "min_zoom": 10, "max_zoom": 17, "tile_format": "png", "tile_size": 256, "origin": "top_left", "output_dir": "D:/tiles_demo", "thread_count": 8, "skip_exists": true }source 字段指定地图源,我这里填的天地图矢量底图,其他源按工具内置的源名称填。bounds 是切图范围的经纬度边界,顺序是西经、南纬、东经、北纬,也就是左下角和右上角。min_zoom 和 max_zoom 决定金字塔从哪一级开始、到哪一级结束。tile_size 大部分底图是 256,有些高清源输出 512,注意和前端的请求参数保持一致。origin 是刚才提过的 y 轴方向,top_left 对应 XYZ 规则,如果用 TMS 挂载就改成 bottom_left。output_dir 尽量短,不要带中文和空格,后面避坑章节会专门说原因。thread_count 是并发线程数,机械硬盘建议 4 到 6,固态硬盘可以拉到 12 以上。skip_exists 表示已经存在的瓦片直接跳过,适合二次补切。
这份配置保存以后,回到主界面加载任务,点击执行。切图引擎会先把范围换算成每一级瓦片的 x/y 行列号,然后从源站逐张下载图片写入本地目录。
3.3 参数调整与资源估算:瓦片数量、线程数与磁盘空间
新手最容易犯的错是不估算规模就开始全级别切图,结果切到一半磁盘爆了,或者网速被占满导致别人办公都卡。瓦片数量有个简单估算方法:每一级的瓦片数大约等于 (2^zoom)^2,但实际切图只有范围内那些瓦片。对工程应用来说,直接数边界对应的 x/y 差距乘起来最快。以下表为参照,z=10 时某市级范围通常只有几百张,z=15 就涨到几万张,z=18 可能上百万张。图片平均大小按 30KB 到 80KB 算,百万张瓦片意味着至少 30GB 磁盘空间,这是必须提前留出来的。
| 缩放级别 z | 全球瓦片数(约) | 市级范围瓦片数(约) | 预计磁盘空间 |
|---|---|---|---|
| 10 | 104 万 | 数百 | 10~30 MB |
| 12 | 1677 万 | 数千 | 100~300 MB |
| 14 | 2.68 亿 | 数万 | 1~3 GB |
| 16 | 42.9 亿 | 数十万 | 10~30 GB |
| 18 | 687 亿 | 数百万 | 30~80 GB |
线程数不是越大越好。源站都有并发限制,开太多线程,单张瓦片下载失败的概率会大幅上升,失败后重试又占用更多时间。我在某次切省级影像图时把线程从 8 调到 20,结果失败率从 1% 涨到 15%,最后反而更慢。一般市区级范围用 8 线程,省级以上范围用 4 线程比较稳妥,网络环境好的内网源可以尝试 12。
3.4 执行切图后的出片抽查
任务跑完以后,界面显示完成率 100% 不代表瓦片都能用。我会先到输出目录看总文件数,再随机抽几个层级内的瓦片用图片查看器打开。重点检查两类位置:一是范围边缘的瓦片,看是否出现半黑边或者内容缺失;二是高缩放级别下的密集区域,比如城市中心,看有没有灰色格子。如果下载阶段触发了源站限流,部分瓦片会是空白或纯色块,这种错后不容易被总数看出来。
命令行下快速核对目录结构和文件数量,用两个命令:
tree /f D:/tiles_demo | head -n 80 find D:/tiles_demo -type f -name "*.png" | wc -ltree 输出前 80 行,能直观看到 z/x/y 三级目录是否存在,文件名是否符合预期。find 加 wc 统计 png 总数,和任务计划里算出的瓦片数量对比,差异超过 1% 就一定有问题。注意在 Windows 下如果你的命令终端是 PowerShell,find 的语法会略有出入,建议直接用系统自带的 cmd 或者是换用下面的 Python 审计脚本,这个在最后一章有完整版本。
4. 避坑指南:切图最容易翻车的六个环节
4.1 瓦片编号对不上:原点设错导致整片错位
现象:前端加载瓦片后,地图整体上下翻转或者偏移一个级别到若干公里。
原因:这是切图任务里 origin 参数和前端框架不匹配导致的。工具按 top_left 切出来的目录,z/x/y.png 中 y 从北往南递增,前端如果用 TMS 协议请求瓦片,会把 y 值反向理解,地图表现为垂直翻转。反过来,你按 TMS 切图,Leaflet 默认的 XYZ 加载就会翻转。
解决:切图前确认前端加载方式。用 Leaflet 就选 top_left,用 GeoServer 发布 TMS 则选 bottom_left。如果瓦片已经切完不想重切,可以写一个小脚本遍历目录名,把所有 y 值替换成 (2^z - 1 - y),文件级翻转,但文件名也得跟着改,工作量不比重切小。
4.2 切出大量黑块白块:源站限流与请求失败的锅
现象:任务显示完成,但抽查时发现一批瓦片是纯白色或者纯黑色,甚至带一个“无法加载”的图标。
原因:大多数在线地图源对并发请求有频控限制,高频请求会触发临时封锁或返回占位图。切图工具默认的重试次数不够,或者重试间隔太短,失败后就直接跳过了。
解决:遇到这种情况,先把线程数调低到 4 以下,再把下载超时时间和重试间隔改大。MapCutter 这类工具一般有失败记录列表,切完以后导出失败清单,只对清单里的瓦片执行补切,而不是把全图再跑一遍。
4.3 内存与磁盘双爆:大范围切图的正确姿势
现象:任务跑到一半,机器卡得鼠标都动不了,随后提示磁盘空间不足被迫中断。
原因:一次性把全省或全国范围都圈进了任务,且 min_zoom 到 max_zoom 跨了 8 级以上。调度器需要维护大量下载任务的状态,内存占用高;瓦片文件数量巨大,磁盘写入跟不上就会堆积在缓存目录。
解决:把范围切成小块,按行政区或格网分批执行。每一批只切 3 到 4 个连续级别,不要从 z=1 直接切到 z=17。磁盘至少预留最终瓦片体积的 1.5 倍,因为部分工具在下载过程中会先生成临时文件再写到目标目录。血泪经验:宁可多建几个任务手动排队,也不要一次贪大。
4.4 高纬度地区拉丝变形严重:墨卡托投影的物理现实
现象:从切出来的瓦片看,北方城市在横向上被拉得很宽,道路和建筑形状比实际扁平。
原因:Web 墨卡托投影在高纬度存在面积变形,纬度越高,形变越明显。切图工具忠实按 3857 投影切片,切出来的瓦片就是拉伸后的样子。
解决:这不是工具 bug,是投影本身的特性。高纬度区域如果用于面积计算或精确测绘,不要用 Web 墨卡托瓦片,应该改用对应地区的本地投影数据再切。如果只是展示路网,接受变形即可,不要试图通过调整切图参数去“修正”,那只会引入新的偏移。
4.5 Windows 下输出目录路径过长导致写盘失败
现象:切图过程中提示“文件路径过长”或“找不到指定路径”,但同样的任务放到别的机器上又正常。
原因:Windows 的 MAX_PATH 限制是 260 字符。当输出目录嵌套较深,加上 z/x/y 这样的三级子目录和文件名,路径很容易超长。中文目录名在部分编码环境下还会占用更多校验位,问题出现得更频繁。这类问题没有任何规律,属于随机抽风型玄学故障。
解决:输出目录放到盘符根目录下,命名尽量短,比如 D:/tiles 而不是 D:/我的项目/地图数据/测试切图输出/最终版。如果项目特别大,还要注意文件名里不要带版本号后缀。路径短一截,省一堆事。
4.6 多个地图源叠加后偏移:坐标基准不统一的终局问题
现象:高德瓦片和天地图瓦片在图上同时显示,道路错开几十米到几百米,缩放级别越高越明显。
原因:两个源用了不同的坐标基准,高德的 GCJ02 和天地图的 CGCS2000/WGS84 之间存在系统性偏移。切图工具只负责切自己数据源的瓦片,不会对不同源的坐标系做统一配准。
解决:尽量不要混用不同基准的源做叠加。如果底层用高德,业务图层就也走高德坐标体系,不要拿 GPS 的原始 WGS84 轨迹直接叠上去。必须叠加时,先做坐标转换,把 GPS 坐标转成 GCJ02 再上图层,但这个转换结果仍然有几米到十几米的残余误差,量测精度要求高的场景别这么干。地图这事儿没有后悔药,选准一个基准从一而终,比后期做各种纠偏都稳。
5. 进阶:用脚本做切片审计、静态瓦片挂载与增量更新
5.1 用 Python 全量审计瓦片树
切图完成以后,用脚本扫一遍目录,比肉眼抽查可靠得多。下面这个函数会检查每一级是否缺行目录、行内是否缺列、以及是否存在空文件,并输出统计结果:
from pathlib import Path def audit_tiles(root: Path): levels = sorted( [p.name for p in root.iterdir() if p.is_dir() and p.name.isdigit()], key=int ) for z in levels: z_dir = root / z x_dirs = sorted( [d.name for d in z_dir.iterdir() if d.is_dir() and d.name.isdigit()], key=int ) if not x_dirs: print(f"z{z}: 无任何 x 行目录") continue for x in x_dirs: x_dir = z_dir / x y_tiles = list(x_dir.glob("*.png")) # x 行目录存在但没有任何 png,视为异常 if not y_tiles: print(f"z{z}/x{x}: 目录存在但无瓦片") total = sum(1 for _ in root.rglob("*.png")) print(f"审计完成,总瓦片数: {total}")函数遍历时按 z 级和 x 行分组,只检查“存在 x 目录但里面没有瓦片”和“全部级别无数据”两类异常。参数 root 传入输出目录的 Path 对象即可。这里没有做严格的行列连续性检测,因为范围边界本身就不是矩形,边缘缺瓦片是正常的。真要做连续性检查,必须先拿到任务配置里的 bounds 和 z 范围,按边界计算期望 x/y 区间,再和磁盘实际文件对比。
5.2 本地瓦片目录直接变成 XYZ 服务
离线瓦片切完以后,不需要额外写后端服务,用 Nginx 指向瓦片目录就能直接对前端提供 XYZ 服务。配置如下:
server { listen 8080; root D:/tiles_demo; location /tiles/ { alias D:/tiles_demo/; add_header Access-Control-Allow-Origin *; expires 7d; } }location 块中的 alias 把 /tiles/ 请求映射到物理目录,前端加载时地址写成 http://localhost:8080/tiles/{z}/{x}/{y}.png,Leaflet 的 TileLayer 直接认这种格式。expires 设置缓存时间,离线路网场景一般表现稳定。注意如果切图时用了 TMS 的 bottom_left 命名,前端会请求不到文件,需要在 Nginx 做一层 rewrite 把 y 翻转,或者干脆回到第 4 章第 1 小节改文件名。
5.3 增量更新:只切新范围,不要全量重跑
底图数据更新时,全量重切是最笨的办法。合理做法是保存上一次切图任务的 bounds 和 zoom 范围,把新变化区域单独建任务,只切新增部分的瓦片,输出目录指到同一个地方,开启 skip_exists 跳过已有瓦片。这样新增几个小区块就补几个区块的瓦片,耗时从几小时降到几分钟。需要注意增量任务的文件名和原任务完全一致,包括 origin 和 tile_size,否则新旧瓦片之间会有肉眼可见的缝隙。
我这个人以前吃过大亏,切完图直接打包丢给前端,既不审计也不对比数量,结果地图中部的空白区域被用户截图发过来好几次。从那以后,我每次切完图都会强制跑一遍审计脚本、统计文件数量、抽样打开边缘瓦片,确认无异常了才交付,这个习惯帮我省掉了大量售后问题。希望这份工具的操作思路和踩坑记录能帮到你。
本文还有配套的精品资源,点击获取