资讯动态

百度地图瓦片下载与离线地图制作:从行列号计算到坐标纠偏实战

发布时间:2026/10/15 20:18:13 来源:尧图企业网站定制
简介一份面向地图开发者和离线应用设计者的百度地图瓦片批量下载工具解决在无网络或内网环境下获取指定区域地图数据的痛点。工具基于Java封装内置坐标转换、范围选择、级别设定、批量下载及数据整合等模块可自动完成百度坐标与瓦片坐标的换算按经纬度范围和缩放等级抓取对应图片适合离线导航、地理信息可视化、应急预置底图等场景。资源包共3个文件包括可执行jar包、properties配置文件和使用说明txt整体仅有5.05MB轻量易部署通过修改配置文件即可指定下载区域与级别无需复杂安装。目前已有5543人学习/下载。下载后可直接获得对应级别的瓦片图片集为后续拼接离线地图、叠加自定义标注提供数据基础可显著减少手工切图工作量适合需要快速搭建本地地图底图并进一步开发个性化地图应用的工程师。1. 百度地图瓦片下载工具把在线底图转成可离线复用的瓦片目录百度地图瓦片下载工具最直白的用法是把指定城市或区域的在线底图拆成一张张 256×256 的 PNG 小图按缩放级别和行列号存进本地目录这套小图矩阵就是地图客户端真正加载的东西。手动画不来也不该拿截图去凑下载工具的价值是把本来只存在于在线服务里的瓦片资源变成你能离线调度、拼图、挂接的数据文件。对正在做离线地图、户外采集、GIS 二次开发或应急通信展示的开发者来说它是把在线地图资产化的必经环节。需要强调的是地图瓦片有自己严格的坐标系和编号规则先把这两件事弄明白跑任务才不翻车。2. 瓦片编号背后的坐标系行列号、缩放级别与范围选择逻辑2.1 百度地图瓦片为什么不能直接套用普通 Web Mercator 行列号很多人的第一反应是地图瓦片不都是按 Web Mercator 网格来切的吗左上角是 0,0x 向右递增y 向下递增最多是 y 轴方向和 TMS 相反。这个说法对大部分国际地图服务成立但对百度地图要打一个折扣。百度地图在线上渲染时使用的是一套经过偏移处理的地理坐标系通常称为 BD-09它基于国测局加密坐标再做了二次偏移投影方式也不是标准 EPSG:3857 的直接展开而是与之近似的百度墨卡托。简单说你拿 WGS84 经纬度按标准公式算出瓦片行列号在这个工具里可能在大部分区域能对上但到了某些城市边缘或大比例尺下会差出半个瓦片甚至一整块。下载工具会替你做的是“请求参数层面的坐标转换”也就是把输入范围换算成百度瓦片服务器认的那套行列号而不是让你自己写投影公式。但你在使用成果时要清楚下载下来的瓦片默认带有百度坐标系的偏移不可能和 GPS 原始轨迹直接叠加。我一般会在下载前先想清楚最终要给谁用如果只做离线浏览直接存原始瓦片即可如果后面要叠加 GPS 轨迹或者和标准 Web Mercator 数据混用就得在下载参数里开启“坐标纠偏”或在拼接阶段处理。2.2 从经纬度换算瓦片行列号给你一套可验证的手算路径虽然工具内部会换算但建议你至少能手算一次。原因很简单你只有知道行列号范围才能预估瓦片数量、检查下载结果是否缺行缺列也才能在拼接阶段判断边界是否有问题。标准 Web Mercator 瓦片的行列号公式在多数参考书上都能找到写成 Python 是这样import math def lnglat_to_tile(lng, lat, zoom): 根据经纬度和缩放级别计算瓦片行列号 返回 (x, y)即列号和行号 n 2 ** zoom x (lng 180.0) / 360.0 * n lat_rad math.radians(lat) y (1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n return int(x), int(y) # 以某个城市中心点为例计算 15 级瓦片坐标 print(lnglat_to_tile(116.404, 39.915, 15))这段代码用了 Python 3 内置的asinh函数避免了老式写法里log(tan 1/cos)在极点附近的溢出问题。zoom就是缩放级别百度地图常用 1 到 19 级15 级对应城区街道尺度。算出来的 x 和 y 是浮点数向下取整后就是这块瓦片在网格中的位置。要注意的是这条公式对应的是标准 Web Mercator百度瓦片实际请求时还要加偏移所以它更适合用来估算范围和数量不适合直接拼 URL。估算数量时用到的公式更直观如果范围跨了 x 方向 120 列、y 方向 80 行总瓦片数就是 120×809600 张。按每张 30 到 80 KB 估算单层结果大约 300 到 700 MB。这个数量会影响磁盘规划、并发设置和下载时长。我习惯在每次任务开始前先写个脚本把范围换算成行列号再输出预计数量和体积确认没问题再启动下载这个习惯帮我避免过好几次把磁盘塞满的尴尬。2.3 工具的三种范围模式和产物形态这类下载工具通常支持三种指定范围的方式矩形对角框选、多边形边界框选、按中心点和半径画圆。矩形模式适合城市或区县多边形适合沿河流、道路或自定义行政区边界圆形多用于基站覆盖、应急保障这类场景。范围越规则生成的瓦片矩阵越整齐后续拼接越容易多边形边缘会出现半块瓦片一般工具会补全整块但边缘会有超出范围的内容这是正常现象。产物目录结构大同小异常见组织方式是tiles/ 15/ 51320/ 20135.png其中15是缩放级别51320是 x 列号20135.png是 y 行号。这种 z/x/y 结构是目前瓦片服务事实上的通用标准后续用 Nginx、MinIO 或自建瓦片服务直接指到这个目录就能用。工具一般还会在旁边生成一个task.json记录本次任务的坐标系、范围、级别和源类型这个文件别删它是后续拼接和追溯的依据。3. 工具实操从配置文件到批量落盘完整跑通一次任务3.1 用配置文件锁定范围、层级与输出目录图形界面是这类工具的标配但批量使用和管理时会遇到一个问题手工填参数不可重现下次再下载同样范围还要重新点一遍。我习惯在有命令行入口的工具上把参数写成 JSON 配置文件让任务有痕迹、可迭代。一份典型的配置长这样{ task_id: demo_area_z15, source: baidu_vector, style: pl, zoom_levels: [12, 13, 14, 15], bounds: { min_lng: 116.35, min_lat: 39.85, max_lng: 116.45, max_lat: 39.92 }, output: ./tiles/demo_area, concurrency: 8, retry_times: 3, resume: true, skip_existing: true }source指定数据源类型常见值是baidu_vector和baidu_satellite分别对应矢量底图和卫星影像。style是地图样式矢量底图一般用pl卫星影像有纯影像和带标注两种带标注的适合做现场对照。zoom_levels用数组而不是单值下载器会自动从低级别往高级别逐级爬。bounds是经纬度范围注意这里用的是 WGS84 坐标工具内部会再做偏移换算。concurrency控制同时请求的瓦片数量retry_times是失败重试次数resume和skip_existing组合使用可以实现断点续传。参数里最容易被忽略的是style。同一个范围下矢量底图和卫星影像的瓦片大小差很多卫星影像单张可达 100 KB 以上矢量底图只有 20 到 50 KB。如果混在一起下载磁盘容量估算会偏差很大。我一般只会为同一个区域同时保留矢量底图和卫星影像标注图只在需要现场比对时下。3.2 缩放级别与并发数怎么选不是越高越好缩放级别选择是新手最容易踩坑的地方。级别越低整幅图越宏观瓦片总数越少级别越高画面越细瓦片数量按 4 的幂次增长。同一个城市范围13 级可能只有几千张到 17 级就是几十万张18 级破百万也很正常。工具默认会把 1 到 19 级全部勾上如果直接跑全量下载时长和磁盘占用会非常可观。更合理的做法是按下发场景倒推级别做城市宏观展示选 10 到 13 级做街道级业务选 14 到 16 级做现场设备定位或施工图底图选 17 到 19 级。所谓“下载范围外扩”指的是在目标边界外增加一圈瓦片通常外扩 1 到 2 个瓦片宽度就够了这样拼接后的图边缘不会露出难看的白边或半截内容。并发数也同理。并发太高容易触发服务端限流导致大量空文件和零星 503并发太低几十万张瓦片要跑好几个小时。我的经验是内网环境下并发 16 到 32 都试过公共服务下稳定跑 8 比较安全。工具一般也提供请求间隔参数单位为毫秒设 50 到 200 可以明显降低被断连的概率。3.3 启动任务和读取日志配置文件准备好之后命令行调用方式一般是这样./tile_downloader --config ./config/demo_area.json --log-dir ./logs/tile_demo--config指定配置路径--log-dir指定日志目录。启动后终端会滚动输出每批瓦片的下载进度、失败数量和重试状态。任务结束或中断后日志目录里会保留每个级别的统计文件包括已下载数、失败数、重试次数。判断任务有没有真正跑完不要只看进度条要看最后一行有没有输出任务结束标记和总耗时。日志里有几个字段要特别留意total表示当前级别计划下载的瓦片总数fetched是实际成功的数量failed是最终失败且重试无效的数量empty代表 HTTP 200 但内容为空文件的异常瓦片。如果empty超过总量的 1%就要停下来调整并发或请求间隔而不是继续让任务空转。我在命令行前通常会先加一条检查命令确认输出目录是空的或者符合预期防止skip_existing把上一次残留的旧瓦片当成新成果find ./tiles/demo_area -name *.png | wc -l如果数量明显不对先清理目录再跑新任务省得后续做数据完整性分析时被脏数据干扰。4. 下载后的三步处理巡检、拼接、配准4.1 先做瓦片目录巡检数量、体积、空洞一次看清下载任务显示“完成”并不代表成果一定可用。瓦片服务在高并发下有时会返回一个 200 状态码的空文件或者层级目录缺了一部分。工具一般不会把所有异常都标成失败因为它只确认“请求完成”不负责验证“文件有效”。所以我拿到结果后的第一步永远是巡检。写一个简单的 Python 脚本统计目录状态import os def scan_tiles(root, zoom): 统计某个缩放级别下的瓦片总数、空文件和异常小文件 total 0 empty 0 small 0 base os.path.join(root, str(zoom)) for x_name in os.listdir(base): x_path os.path.join(base, x_name) if not os.path.isdir(x_path): continue for y_name in os.listdir(x_path): path os.path.join(x_path, y_name) total 1 size os.path.getsize(path) if size 0: empty 1 elif size 200: small 1 return {total: total, empty: empty, small: small} print(scan_tiles(./tiles/demo_area, 15))这里以 200 字节作为异常阈值是一个经验值正常瓦片压缩后至少几十 KB只有极少部分纯色块可能压到几 KB 以下但如果大量文件都在 200 字节以下基本可以断定下载过程出了问题。巡检发现空洞时不要重新全量下载工具一般支持按行列号范围补下把缺的范围单独列出来跑一次即可。补下后的目录和原目录合并用skip_existingtrue可以跳过已有文件。4.2 把瓦片拼接成一张大图有些业务场景不需要保留瓦片目录而是要一张完整的大图比如做打印出图或汇报演示。拼接的思路是把某级别下的所有瓦片按照行列号排列到一张大画布上行列号不连续的位置留白或跳过。直接上代码from PIL import Image import os def merge_tiles(tile_root, zoom, x_range, y_range, out_png): tile_root: 瓦片根目录, 内部按 zoom/x/y.png 存放 x_range, y_range: 需要拼接的行列号范围 tile_size 256 x_min, x_max x_range y_min, y_max y_range width (x_max - x_min 1) * tile_size height (y_max - y_min 1) * tile_size canvas Image.new(RGB, (width, height), white) for x in range(x_min, x_max 1): for y in range(y_min, y_max 1): path os.path.join(tile_root, str(zoom), str(x), f{y}.png) if not os.path.exists(path): continue tile Image.open(path).convert(RGB) canvas.paste(tile, ((x - x_min) * tile_size, (y - y_min) * tile_size)) canvas.save(out_png) print(f已保存 {out_png}尺寸 {canvas.size})这段代码的关键点是Image.new用RGB模式而不是默认的RGBA。很多地图瓦片本身带透明通道直接贴到透明画布上会出现边缘黑线或花屏白底可以避免大部分问题。tile_size固定为 256这是绝大多数在线瓦片的标准尺寸。如果下载工具支持 128 或 512 尺寸记得改这里。拼接后的图片已经是一张完整栅格图但此时它还没有任何地理参考只是一个长方形图片文件不能直接进 GIS 软件和真实坐标对齐。4.3 把拼接结果挂上地理参考转成 GeoTIFF如果要把成果导入 GIS 工具或叠加其他空间数据需要把拼接图写成带地理参考的 TIFF。这里不能简单地把百度瓦片当标准 Web Mercator 出图因为瓦片本身带偏移。常见做法是先用工具在下载时开启“坐标纠偏”得到 WGS84 偏向的瓦片再以 EPSG:3857 为输出投影做一次重采样。用 GDAL 完成这一步比较直接from osgeo import gdal, osr def add_georeference(merged_png, out_tif, origin_x, origin_y, res): merged_png: 拼接好的大图 origin_x, origin_y: 左上角像素对应的投影坐标 res: 每个像素对应的地面分辨率 ds gdal.Open(merged_png, gdal.GA_ReadOnly) width ds.RasterXSize height ds.RasterYSize target_ds gdal.GetDriverByName(GTiff).Create( out_tif, width, height, ds.RasterCount, ds.GetRasterBand(1).DataType ) target_ds.SetGeoTransform([origin_x, res, 0, origin_y, 0, -res]) srs osr.SpatialReference() srs.ImportFromEPSG(3857) target_ds.SetProjection(srs.ExportToWkt()) for band_idx in range(1, ds.RasterCount 1): band ds.GetRasterBand(band_idx) data band.ReadAsArray() target_ds.GetRasterBand(band_idx).WriteArray(data) target_ds None print(f已生成带坐标的 GeoTIFF: {out_tif})origin_x和origin_y来自拼接图左上角那块瓦片左上角像素的真实坐标计算办法是把边缘瓦片的行列号换算回百度墨卡托坐标。如果你下载时开了纠偏就直接用 EPSG:3857 的坐标范围如果没有需要先做一次偏移纠正否则叠加到标准底图上会差几百米。这也是很多人下载百度瓦片后“总觉得位置不对”的根本原因。做完以上三步一套可交付的离线地图成果才算真正完成。后面第五部分我会专门讲几个容易反复踩的坑。5. 避坑瓦片下载里最容易翻车的五个地方5.1 拼接后出现黑色边缘和花屏现象单独打开任意一张瓦片都正常但拼接完成后相邻瓦片的接缝处出现黑线、黑边或者整块泛花。原因瓦片本身是带透明通道的 PNG白色道路和地块是半透明叠加透明区域在直接粘贴到黑色背景或透明画布时会被渲染成黑色。解决拼接时把画布固定为白色底读取瓦片后先执行convert(RGB)把 alpha 通道丢掉。另一点是不要用系统自带看图工具做最终验证很多看图工具对透明通道处理不规范最好用 GIS 软件或 Python 重新打开检查。5.2 大量文件大小为 0 或不足 200 字节现象下载任务显示成功但目录统计发现几百个文件是 0 字节或者大小只有几十字节。原因并发请求过高时触发服务端限流服务端返回了 200 状态码但没有图片内容也存在代理服务器在连接中断时生成了截断文件。解决先把并发数降到 4 到 8增加请求间隔。然后启用工具的失败重试和空文件重试机制。如果仍然出现可以在巡检脚本里加上“文件小于 200 字节自动删除并记录”再针对这些坐标单独补一次小范围下载。5.3 下载成果和 GPS 轨迹叠加后整体偏移现象瓦片底图显示正常但把 GPS 轨迹或标准 Web Mercator 数据叠加上去后整体偏移几百米有时偏到相邻街区。原因百度瓦片使用 BD-09 坐标系本质上是加密偏移后的坐标直接按 WGS84 或 EPSG:3857 处理当然对不上。解决下载参数里开启坐标纠偏或者对拼接后的 GeoTIFF 做一次从百度坐标到 WGS84 的转换。需要注意坐标纠偏不是简单的常数平移不同城市偏移量不同必须用算法处理不要手动选一个偏移量套用全部区域。5.4 磁盘空间预判不足任务中途失败现象下载到一半磁盘写满任务中断日志里大量写入失败记录。原因忽略了卫星影像的单张体积或者同时勾选了十几个缩放级别实际下载量比预期高出一个数量级。比如一个城区在 17 级往往超过 10 万张瓦片每张 50KB 就是 5GB 以上如果范围再大一点很容易爆盘。解决下载前先用行列号范围公式估算数量按每张瓦片最大 100KB 做容量预算。任务配置里限制级别列表不要全选。给输出目录预留两倍预算空间因为中间文件可能还要再做拼接。5.5 断点续传失效重启任务后从头开始现象任务跑到一半手动中断再次启动后本应跳过已有文件结果又开始全量扫描或重新下载。原因配置里漏开resume和skip_existing或者输出目录换了新路径工具识别不到旧文件。解决启动前确认配置中resume: true且skip_existing: true关键是输出目录不要随意更改。工具一般通过输出目录下的元数据文件识别任务删除或改名都会让它当成全新任务处理。再补充一个习惯任务中断后不要急着重启先看日志确认已完成的瓦片目录结构正常再决定是续跑还是补下。6. 进阶用法把瓦片工具接进自动化巡检脚本交付前跑一次全量自检工具本身能完成下载但离“可交付的离线地图包”还差一道质检。我把下载、拼接、巡检三个动作串成一条流水线每次交付前跑一次自检脚本专门验证瓦片目录有没有空洞、层级是否齐全、拼接图大小是否符合预期。这步做完再交付基本能避免“现场打不开、拼接缺块、坐标对不上”这三类事后返工。巡检脚本核心逻辑是遍历每个缩放级别下的 x 目录确认每个 x 目录下都有对应的 y 文件import os def validate_package(root, zoom_list, expect_ranges): zoom_list: 需要校验的级别 expect_ranges: 每个级别预期的行列号范围和配置文件中一致 report [] for zoom in zoom_list: base os.path.join(root, str(zoom)) x_min, x_max, y_min, y_max expect_ranges[zoom] lacking [] for x in range(x_min, x_max 1): x_dir os.path.join(base, str(x)) if not os.path.isdir(x_dir): lacking.append((x, missing_dir)) continue for y in range(y_min, y_max 1): if not os.path.exists(os.path.join(x_dir, f{y}.png)): lacking.append((x, y)) report.append({zoom: zoom, lacking_count: len(lacking)}) return report print(validate_package(./tiles/demo_area, [12, 13, 14, 15], {12: (100, 110, 50, 60), 13: (200, 220, 100, 120)}))这个脚本的价值在于把“主观觉得没问题”变成“数据上确认没问题”。每次调度新任务前我也会把配置文件和这个脚本一起存到任务目录里形成一套可追溯的记录。瓦片下载这类工作最怕的不是下载失败而是下载成功但成果不能用。从那以后我每次交付瓦片资源都强制自己先跑一遍小范围自检再把巡检脚本挂进任务流程确认数量、大小、坐标都正常后才做全量。这个习惯救回过不止一次上线前夕的返工也希望它能帮到你少踩几个坑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑