
简介面向需要离线地图能力的 C# 开发者资源为基于 GMap.NET 封装的地图下载执行文件与配套类库包含可直接运行的 MapDownloader.exe 图形工具支持选定区域、缩放级别与地图源批量下载瓦片也提供 GMapDownload.dll、GMapUtil.dll、GMap.NET.Core.dll、GMap.NET.WindowsForms.dll 等动态库方便在 WinForms/WPF 项目中引用并实现离线缓存、分组绘制与地图数据管理。压缩包共 61 个文件以 dll、pdb 为主同时包含 png/js/xml/css 界面资源与 gmdb 地图缓存数据库文件整体约 15.33MB结构清晰、便于按需取用。配套工程还集成 log4net、Newtonsoft.Json、MySql.Data、NPOI、Aspose.Cells 等依赖覆盖日志记录、JSON 解析、数据库访问及地图数据导出等常见需求。资源已有 2117 人学习下载适合希望快速搭建离线地图下载方案、减少轮子开发的入⻔级至中级开发者。1. gmap离线地图下载执行文件先搞清楚它到底解决什么事做GIS项目的人迟早会撞上这么一件事客户的内网没有外网但地图底图不能空着或者你在野外跑现场手机信号时有时无在线瓦片缓存满了就变灰。gmap离线地图下载执行文件就是专门把在线地图按“瓦片金字塔”规则切好、拉下来、存成本地文件或数据库的工具。它解决的不是“截几张图存手机里”这种临时需求而是让后续项目的渲染程序可以直接读取本地瓦片离线也能出图。这篇文章写给要在本地构建离线地图底图的人新手照着配置能跑通下载熟手能避开坐标系、切片规则、断点续传这些真正的坑。2. 离线地图不是截图gmap瓦片方案的工作原理与选型理由地图离线下载的底层逻辑是把一张大地图拆成固定大小的瓦片通常是256×256像素按缩放级别分层存储。在线地图服务商早就这样做了浏览器请求的是当前视野内、当前级别下的几十张瓦片服务器按某个公式计算这些瓦片的编号。gmap离线下载工具做的就是同一件事只是把“请求即用”变成“提前批量拉取并落盘”。2.1 瓦片金字塔与坐标换算为什么离线地图必须按分级分块存先说瓦片编号规则。绝大多数web地图采用Web墨卡托投影把经纬度映射到以米为单位的平面坐标再在每一缩放级别把整个地球切成正方形瓦片。缩放级别从0开始级别0是一整张世界图。级别1横向纵向各分成2块级别n就是2^n × 2^n块。每个瓦片有唯一的行号x、列号y加上级别z共同组成一个三维索引。这个金字塔结构决定了离线下载的粒度。你不必下载某个级别下所有瓦片只需要目标地理范围对应的矩形内的x、y集合。工具内部一般都会实现两套换算一是经纬度 → 瓦片坐标用于确定“该下载哪几张”二是瓦片坐标 → 经纬度用于把下载结果挂到其他地图组件上时做边界校验。很多人误以为离线地图就是“把屏幕截屏拼起来”这是最大的理解偏差。截屏没有坐标系放不进GIS软件。瓦片则带有明确的空间位置信息渲染引擎通过同样的换算公式在任意位置、任意缩放级别下找到对应瓦片并拼接。因此离线地图工作得越好越依赖你选择的切片规则和下载工具完全一致。坐标换算是第一个坑但也是可验证的。下面这段代码是通用的经纬度转瓦片坐标算法适用于所有Web墨卡托系的地图源import math def lat_lon_to_tile(lat, lon, z): # 经纬度转瓦片坐标返回 (x, y) n 2 ** z x int((lon 180.0) / 360.0 * n) lat_rad math.radians(lat) y int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n) return x, y print(lat_lon_to_tile(39.9042, 116.4074, 10))这段代码用标准Web墨卡托公式计算瓦片坐标先按经度线性映射得到x再通过反双曲正切变换得到y。核心是asinh(tan(lat_rad))这一步它把纬度的等距分布变为墨卡托投影下的纵向分布。参数z代表缩放级别n是当前级别的总行列数。实际使用时如果发现下载出来的瓦片位置偏移多半是这个公式里的投影方式与在线地图采用的投影不一致换用开放街道图源常用的公式也同理差异只在误差处理上。关于瓦片坐标换算还有一个经常被忽略的细节是边界取值。当经纬度恰好落在瓦片边缘时不同工具的向上取整或向下取整策略可能不同结果就是下载任务多算或少算了一行。我一般会在本地用一个小脚本把范围四角的瓦片坐标都打印出来手动验证一下极值比直接信任工具的日志可靠得多。2.2 对比两种落地格式文件目录瓦片 vs 打包数据库怎么选下载完成后的存储格式直接影响后续加载性能和实施复杂度。最常见的两种格式是“目录瓦片”和“打包数据库”。目录瓦片的结构是{级别}/{x}/{y}.png这样的文件夹层级一个瓦片一个文件。优点非常直观无需额外依赖任何静态文件服务器甚至直接文件系统读取就能用也方便人工抽查某个瓦片是否存在。缺点在于海量小文件会让文件系统元数据膨胀拷贝大量瓦片到内网机器时耗时可能比下载还慢在桌面系统上尤其明显。打包数据库则把所有瓦片写进单个文件典型如MBTilesSQLite格式。优点是一个文件搞定移动、备份都很轻松加载时通过索引查询速度稳定。缺点是客户端必须带有嵌入式数据库读取能力而且无法直接看到某块瓦片的内容调试时不那么直观。我用一个表格把决策维度固定下来对比项目录瓦片打包数据库MBTiles单文件数成千上万个小文件1个文件拷贝/备份慢元数据开销大快客户端依赖无读图片路径即可需要数据库驱动人工检查方便需要外部工具打开适用场景小范围、低级别、静态目录服务跨平台离线包、移动端App选型判断标准并不复杂。如果离线底图只是给某台内网服务器上的地图应用用且范围不大比如一个城市的1到17级目录瓦片够用了。如果要分发给大量终端尤其是手机端或桌面端一个数据库文件远比几百兆碎片文件好管理。我一般会同时生成两种格式先用目录瓦片快速验证下载范围是否正确确认无误后再转成数据库文件分发。另外还有一个易被忽略的点目录瓦片的文件名大小写。常见的客户端对路径大小写敏感服务端可能相反。生成工具里统一输出小写的z/x/y格式能少踩一半乱报404的坑。如果你在做跨平台项目还需要考虑瓦片文件名的扩展名PNG是默认但有些地图源输出的是WebP。数据库格式本身不限制扩展名但目录瓦片的读取端往往靠扩展名判断图片类型下载前先确认源图格式与目标客户端兼容否则要加一个格式转换步骤。这个转换最好在下载端做不要等到分发后再批量转因为那时瓦片数量可能上万转换时间够你重新下载一遍。3. 拿到执行文件下载编译、运行环境与最小启动命令无论你是从发布包直接拿还是自己编译关键是要清楚手里的“执行文件”能干什么、不能干什么。市面上叫“gmap离线地图下载”的工具不少有的带图形界面有的纯命令行。纯命令行版本更适合脚本化、自动化跑批也是很多一线人员最终会选择的方向因为可以把下载任务写进定时任务夜里自动跑。3.1 从源码构建环境准备与依赖说明标题里的“执行文件”其实有两种来源一是直接使用发布包里的可执行文件二是仓库源码自己编译。直接下载发布包当然省事但如果你要修改下载范围规则、叠加自定义地图源或只是想避开不信任的二进制文件本地构建更靠谱。常见做法是用Python或Go编写这类工具因为网络下载和并发处理都很方便。环境准备分三层。先是运行时Python版工具需要3.8以上Go工具则需要1.19以上然后是依赖网络请求、图片校验、数据库写入Python对应requests、Pillow、sqlite3标准库Go对应net/http。再就是构建/打包工具Python可以靠PyInstaller打包成单文件Go直接go build就能出可执行文件。下面以Python版本为例展示最小化构建过程# 创建虚拟环境避免污染系统Python python3 -m venv .venv source .venv/bin/activate # 安装依赖 pip install requests pillow pyinstaller # 执行编译打包Windows上去掉“./”直接写命令 pyinstaller -F gmap_offline.py -n gmap-offline这里最关键是pyinstaller -F参数它把所有依赖打进一个二进制文件-n指定输出文件名。打包完成后会在dist目录生成gmap-offline桌面系统下是gmap-offline.exe。运行它之前先测一下源码脚本本身能否正常工作否则打包只会把运行时的坑原样带上排查起来更麻烦。如果你的需求只是临时用一次不追求分发给别人完全不用打包直接用python gmap_offline.py运行也可以。打包的意义在于目标机器没有Python环境时也能执行这是“执行文件”这个词最常出现的场景。我还遇到过一种情况内网机器禁止安装Python但允许拷入exe文件所以打包成单文件后反而能绕过环境限制这在内网项目里很实用。依赖版本是个容易翻车的地方。比如Pillow在大版本升级后某些验证图片尺寸的接口行为会变requests新版本对HTTP代理的处理也更严格。我一般会在项目目录放一个requirements.txt锁住主要依赖的大版本。不要盲目追求最新工具能跑通比版本新更重要。3.2 最小启动命令命令行参数拆解拿到执行文件后最要紧的事情是用一条最简命令把下载跑通再逐渐加参数。下面的命令是从经纬度范围下载某一级别瓦片的最小示例# 最小命令下载某个城市中心附近10级瓦片输出到./tiles ./gmap-offline \ --lat-min 39.8 --lat-max 40.1 \ --lon-min 116.3 --lon-max 116.6 \ --zoom 10 \ --output ./tiles \ --source osm每个参数含义--lat-min/--lat-max和--lon-min/--lon-max定义下载矩形的边界坐标是WGS-84经纬度--zoom指定级别可以写一个值或范围如10-13--output决定瓦片根目录--source选择地图源常见做法是写内置的名字比如osm表示开放街道图源。我第一次跑这个命令时经常会漏掉--source工具就默认用随机源下载出来的图片风格不一致后来才意识到参数必须显式指定。运行后如果没有立刻报错终端会显示每个线程在拉取瓦片。最快验证成功的方式是等10秒钟然后看./tiles/10/目录下是否已生成png文件。注意最小命令成功不代表范围正确因为工具不会校验当前视野是否正好覆盖你想要的地区。务必用已知坐标比如某个地标反查瓦片编号再去目录里确认那个瓦片存在。命令行里还有一个隐藏参数值得注意--skip-existing。当下载任务中断后重新执行这个参数能跳过本地已存在的瓦片做到“伪断点续传”。但前提是工具实现里对“存在”的判断不能只看文件名还要检查文件大小是否大于0。有的工具只判断文件是否存在结果中断时留下0字节损坏文件重跑也不覆盖浪费大量时间。如果想在脚本里调用建议把参数写入配置文件然后用--config指定。这样每次跑批不必记住一长串命令行也方便把不同城市的任务拆成多个配置文件管理。配置文件里建议包含范围、级别、地图源、输出目录、并发数和重试策略几乎就是把命令行参数结构化落地。4. 配置下载参数经纬度范围、缩放级别、路径与并发下载的成败七成在参数配置上。很多新人下载了一半才发现范围设错、级别太高浪费的不仅仅是流量还有死人般的等待时间。所以这一章讲清楚范围、级别、并发三个核心参数的设定逻辑。4.1 设定下载范围矩形框、圆与行政边界三种方式实际项目中矩形范围最简单但还是有细节左上和右下经纬度要按“西南角、东北角”指定否则工具可能计算出空集合。圆形范围常用于“以某点为中心半径多少公里”的场景。行政边界比如一个区的多边形最精确但对工具的算法要求高需要判断瓦片是否在多边形内。常见工具都支持矩形和圆形行政边界往往要先用别的工具生成GeoJSON再导入。这里展示一个矩形加圆形的配置文件示例{ boundary: { type: circle, center: [116.4074, 39.9042], radius_km: 5 }, zoom: [10, 14], output: ./city_circle, source: osm, threads: 8, retry: 3, timeout: 10 }boundary字段支持rect和circle两种。rect对应四个角坐标circle用中心点和半径更直接。我实际使用中发现圆形范围下载的结果在边缘会有锯齿状缺块因为瓦片是矩形不可能完美贴合圆周。解决的办法是把半径稍扩大0.5公里宁可多下一点别让边缘露出空白。对于矩形范围有一个经常踩的坐标系陷阱如果你从在线地图的URL里直接复制坐标那些坐标通常是投影后的像素坐标不是经纬度。必须先从地图组件里确认输出的是经纬度再填进配置文件。我在某次测试中直接复制了浏览器调试面板里的坐标结果范围跑到了大洋中间下载了半小时才发现。行政边界方式虽然精度最高但工具判断瓦片中心点是否在多边形内的计算开销大下载速度会明显下降。如果范围很大且边界特别复杂建议先用简化工具比如Douglas-Peucker压缩边界点数量。几百个点的边界和几万个点的边界在判断过程中耗时可差数倍。4.2 缩放级别与清晰度怎样选z值范围才不浪费流量缩放级别不是越高越好。级别每加一瓦片数量变成四倍当范围跨几个城市时存储和下载时间会指数爆炸。经验公式级别n时瓦片数量约等于((range_width * 2^n / 360) * (range_height * 2^n / 360))。比如一个边长10公里的矩形在15级大约几百张瓦片17级就可能几千张。对屏幕显示来说15级适合城市概览17级适合街道细节。如果是给打印出图用才需要高缩放到18甚至19级。我在生产项目里通常按“最坏级别数”做预算先列出目标应用最小缩放级别和最大缩放级别计算每个级别的瓦片数量再乘以单张瓦片大小平均40-100KB得到总容量。若超出磁盘或网络预算就砍掉高缩放级别或缩小地理范围。千万不要一开始就写1-19那样往往下载几天都完不成而且绝大多数瓦片永远不会被看到。另外要注意的是不同地图源的瓦片清晰度并不一致。有的源在低级别时只提供粗糙的道路有的源则有高分辨率影像。如果你需要高精度底图先试下载单个瓦片肉眼对比再决定是否采用这个源。某个地图源在18级仍模糊这一级别的下载就是纯浪费。级别范围还可以按用途分段底图用10-13级业务标注层用14-16级影像打印用17-18级。分开下载、分开存储将来更新某一层时不用全部重来。这样配置文件的zoom数组可以写多个区间工具按区间生成独立输出目录。4.3 并发数、超时与重试把下载性能调到不翻车网络下载最怕两类问题并发太高被服务器限流并发太低又太慢。常见工具的推荐值在4到16之间。你可以在配置里设置threads: 8开始时先跑5分钟看日志如果出现大量超时或HTTP 429就降到4如果一直很快且没有失败再升到12。这个调优过程应该用工具提供的“只下载范围内前1000块瓦片”的试跑参数否则每次调整都要等很久。超时和重试则是稳定性关键。timeout设为10秒比较合理因为公共瓦片服务在高峰期响应可能变慢低于5秒会误杀正常请求。retry设为3表示失败后等待并重试。日志里要能看到具体瓦片的失败原因便于判断是网络问题还是参数问题。重试时要注意退避策略连续失败次数多了应暂停一段时间否则只是给服务器添乱。并发数并不是越大越好因为本地磁盘写入也可能成为瓶颈。当线程数超过一定值大量小文件并发写同一目录磁盘IO反而拖慢整体速度。在机械硬盘上尤其明显。我的习惯是先测出当前机器稳定下载的每秒瓦片数比如每秒30张然后观察并发从8调到12时速度是否线性增长。如果增长不明显回到原值别让工具看起来忙忙碌碌但实际吞吐没变化。还有一个处理细节日志级别。生产环境中我喜欢把成功日志关闭只保留失败日志因为下载几十万张瓦片时每一张成功的日志都会让文件变得巨大、查找问题更难。工具如果支持--log-level error一定要用上。失败日志需要记录完整的瓦片坐标和HTTP状态码这样排查时可以精准定位到某个z/x/y。5. 离线地图下载避坑五类常见问题与排查思路离线地图下载的坑翻来覆去就那几类。这一章把最常见的五类写成排查手册每一条都按“现象、原因、解决”三个步骤来方便你遇到问题时直接对照。5.1 下载中断后文件还能续传吗先看工具是否支持断点续传现象下载到一半网络断了重新运行命令发现工具从头开始下载之前下好的瓦片还在但依然从第一张开始请求最后很多瓦片被重复覆盖浪费好几个小时。原因大多数下载工具默认不检查本地已有瓦片因为“下载全部”的逻辑简单、不容易出错。所谓断点续传并非标准功能而是通过--skip-existing这类参数实现的。如果工具没有实现存在性检查重跑就是全量。解决先确认工具是否有跳过已有文件的开关。有则在配置里开启没有则写一段清理脚本删除所有0字节文件和下载日志中标记失败的记录再重新全量跑。更省事的做法是下载前给瓦片目录做快照中断后对比快照列出缺失清单用一个小脚本下载缺失部分。这个脚本不用很复杂只要解析瓦片文件名并判断对应路径是否存在即可。5.2 拼接错位不同地图源或不同时期下载的瓦片混用现象离线地图加载后发现相邻瓦片之间的道路不连续有的地方颜色风格明显不一致甚至出现同一级别的瓦片来自不同投影基础导致边界处内容错位。原因混用了不同地图源的瓦片。很多工具允许--source指定多个源或者下载中途手动换了源但瓦片坐标系和样式差异导致拼接不上。还有一种情况是相同源但在不同时间段下载服务器端瓦片更新了导致新旧瓦片风格不一。解决坚持一个下载任务内只使用同一个地图源和同一个服务器端点。如果确实需要多个源分别下载到不同目录渲染时按图层叠加而不是混拼。下载前把--source写死进配置文件不要依赖交互式选择。如果已经混用了只能删掉重下没有后悔药。5.3 经纬度偏移GCJ-02与WGS-84坐标系混用现象下载出来的瓦片和本地业务数据叠加时偏移了几十米到几百米不等而在下载工具里输入同样的经纬度在地图上看却正常。原因国内常见地图服务使用GCJ-02坐标系而工具默认假设WGS-84。你在在线地图上看到的坐标是GCJ-02直接填进工具后工具按WGS-84换算瓦片坐标自然在WGS-84的瓦片世界里偏出一段距离。解决第一步确认地图源使用哪种坐标系。如果地图源本身就是GCJ-02瓦片工具必须支持坐标系声明参数比如--coord gcj02或者提供偏移纠偏函数。第二步在下载工具的配置里打开坐标系转换开关让工具先用GCJ-02转WGS-84再计算。第三步验证偏移下载一张单瓦片与业务数据叠加看误差偏差明显就检查转换函数是否生效。内网GIS项目这一点最容易翻车因为大家默认在线地图标的就是真实经纬度其实不完全是。5.4 硬盘空间爆满瓦片数量估算严重偏低现象下载任务跑到一半磁盘空间满了工具报“no space left on device”已下载的瓦片文件也不完整删除后重新下载又因空间不足反复失败。原因低估了总瓦片数量。粗算时用了平均瓦片大小但高缩放级别下的影像瓦片可能是普通瓦片的三到五倍而且边界矩形覆盖了水域或空白区域这些区域也有瓦片文件只是内容多为透明或单一颜色同样占空间。解决下载前先用工具的计算模式评估瓦片数量和总体积或者自己按级别累加估算并乘以1.5的安全系数。下载过程中随时用du -sh监控目录大小当达到预期体积的80%时提前检查。还要确保输出目录所在分区有足够空间不要只看系统盘剩余空间数据盘和系统盘是两回事。5.5 瓦片在目标软件中不显示切片规则不一致现象下载的瓦片文件完整但在自己的地图组件中加载一片空白或者显示灰底、拼接错乱。用图片查看器打开瓦片文件又完全正常。原因目标软件的瓦片坐标原点或文件名规则与下载工具不一致。常见的有两种约定一种是以左上角为原点、y轴向下另一种是以左下角为原点、y轴向上。还有的软件要求{z}-{x}-{y}.jpg有的要求{z}/{x}/{y}.jpg斜杠和横杠处理不当就会匹配不上。解决先查阅目标地图组件的文档确认它读取瓦片的路径模板。然后调整下载工具的输出文件名模板或者在渲染端配置路径模板。如果不支持任意目录结构就用一个小脚本把下载后的瓦片目录重命名成目标格式。这个脚本通常只有几十行就是遍历目录重命名花不了多少时间但能一次性解决不显示的问题。6. 把下载结果用起来离线瓦片在项目中的挂载与验证技巧下载完成不是结束真正让离线地图跑起来还需要两步验证完整性和挂载到渲染端。验证完整性我习惯用一个随机抽样脚本从下载目录里随机挑出20张瓦片分别记录其z/x/y然后与内网自建的地图服务器对比同一坐标瓦片的像素内容是否一致。如果20张里任何一张缺失或明显不匹配说明下载阶段就有问题早点排查比事后返工强。挂载方式取决于你的应用是C/S还是B/S。C/S桌面应用通常直接指定瓦片根目录作为数据源B/S应用则把目录瓦片发布成静态资源服务再用标准瓦片协议访问。重点检查两点路径模板是否匹配http://your-host/tiles/{z}/{x}/{y}.png以及HTTP响应头是否允许客户端缓存。离线环境下静态缓存可以让地图缩放更跟手。定期更新是很多人忽略的。地图数据会变离线下载不是一次性的。我会把下载命令包裹成一个定时任务每周增量更新低级别区域每月全量更新业务最近区域。增量更新依赖工具对“已存在瓦片只更新有变化级别”的支持如果没有就只下载高缩放级别的热点区域覆盖面反而更重要。最后分享一个习惯每次下载任务结束后把配置文件、日志摘要、瓦片数量统计一起归档。下次别人问你“这地图哪来的、范围覆盖到哪”直接翻归档就能回答不用再猜。这套流程跑顺之后离线地图下载就是从“玄学”变成“流水线”。希望帮到你。本文还有配套的精品资源点击获取