简介:百度地图瓦片下载工具是一套面向离线地图开发者与GIS应用人员的实用资源,针对无网络环境下地图数据获取难、瓦片批量下载效率低的问题,提供从坐标转换到瓦片落地的完整支持。压缩包共3个文件,包含可执行的jar程序、application.properties配置文件和一份使用必读说明,整体约5.05MB,体积轻量、开箱即用。工具围绕百度坐标系转换、下载范围与级别设定、批量抓取及瓦片整合等环节设计,可帮助读者快速搭建本地地图数据源,用于离线导航、地理数据分析、地图应用开发及教学科研等场景。目前已有5550人学习下载,适合具备一定Java运行环境基础、希望降低离线地图开发成本的中高级开发者参考使用。
1. 百度地图瓦片下载工具:从离线底图到本地缓存的完整落地
做 GIS 开发或者内网可视化项目的人,迟早会撞上一个绕不开的问题:地图底图怎么离线用。在线调用当然方便,但一旦部署环境没有外网、或者需要批量出图、又或者要控制加载速度,就必须把瓦片提前抓下来存成本地目录。百度地图瓦片下载工具.zip 就是干这件事的——它把百度地图的瓦片按层级、按区域批量拉取,输出成标准的 z/x/y 目录结构,方便后续用 Leaflet、OpenLayers 或者自研渲染引擎直接读取。这个工具适合三类人:需要内网部署地图的开发者、做数据大屏要预缓存底图的工程师、以及想研究瓦片切片规则的技术爱好者。它不解决坐标系转换和审图号问题,那是另一层的事。
2. 瓦片下载的核心逻辑:URL 规则、层级与并发控制
2.1 百度地图瓦片的 URL 拼接规则
百度地图的瓦片服务并不是简单的 z/x/y 直接拼路径,它有一套自己的编码方式。常见做法是先把经纬度转成百度墨卡托坐标,再根据层级换算出瓦片行列号,最后拼成类似http://maponline{0-3}.bdimg.com/tile/?qt=tile&x={x}&y={y}&z={z}&styles=pl的请求。这里的{0-3}是负载均衡用的子域名,工具里一般会做轮询,避免单一域名请求过多被限流。
真正容易翻车的是 x、y 的取值。百度地图在部分层级下 x 和 y 会出现负数,直接拼进去请求会返回空图或者 404。我一般会在代码里加一层判断:如果 x 或 y 小于 0,就加上 2 的 z 次方做偏移,再拼 URL。这个细节很多下载工具没处理好,导致边缘区域的瓦片全是空白。
import math def lnglat_to_tile(lng, lat, zoom): """ 将经纬度转换为百度地图瓦片行列号(简化版,实际需先做坐标系转换) lng/lat: 百度坐标系下的经纬度 zoom: 缩放层级 返回: (x, y) 瓦片行列号 """ # 百度地图使用的墨卡托投影参数 x = (lng * 20037508.34 / 180.0) / (20037508.34 / (2 ** (zoom - 1)) / 256) y = (math.log(math.tan((90 + lat) * math.pi / 360)) / (math.pi / 180)) y = (y * 20037508.34 / 180.0) / (20037508.34 / (2 ** (zoom - 1)) / 256) # 百度瓦片原点在左上角,y 轴需要翻转 y = (2 ** zoom) - y return int(x), int(y) def build_tile_url(x, y, z, subdomain_index=0): """ 拼接百度地图瓦片请求 URL subdomain_index: 0-3,用于轮询子域名 """ subdomains = ['maponline0', 'maponline1', 'maponline2', 'maponline3'] # 处理负数行列号:百度地图在低层级边缘会出现负值 max_tile = 2 ** z if x < 0: x += max_tile if y < 0: y += max_tile url = f"http://{subdomains[subdomain_index]}.bdimg.com/tile/?qt=tile&x={x}&y={y}&z={z}&styles=pl" return url上面这段代码里,lnglat_to_tile做的是经纬度到瓦片行列号的换算,实际项目中你拿到的经纬度如果是 GPS 坐标,还得先走一遍 WGS84 转 BD09 的流程,否则位置会偏几百米。build_tile_url里的styles=pl参数控制瓦片样式,常见取值还有sl(卫星图)、pl(普通街道图),改这个参数就能切换底图类型。子域名轮询建议用subdomain_index = (x + y) % 4的方式,比随机取更均匀。
2.2 层级选择与下载量估算
层级每加一级,瓦片数量翻四倍。这不是线性增长,是实打实的指数爆炸。我见过有人想下 18 级全国底图,算完存储直接放弃。下面这张表是不同层级下全国范围(约 960 万平方公里)的瓦片数量估算,按实际经验值取整:
| 层级 | 单瓦片覆盖范围 | 全国瓦片数(约) | 存储占用(PNG,约) |
|---|---|---|---|
| 10 | 约 40km | 200 张以内 | 几 MB |
| 12 | 约 10km | 3000 张左右 | 50 MB 上下 |
| 14 | 约 2.5km | 5 万张左右 | 800 MB 左右 |
| 16 | 约 600m | 80 万张左右 | 12 GB 左右 |
| 18 | 约 150m | 1200 万张以上 | 200 GB 起步 |
选层级的原则很简单:先问清楚最终展示的屏幕分辨率和缩放范围。如果只是内网看个大概,12 到 14 级足够;如果要做到街道级细节,16 级是性价比最高的选择,再往上就得考虑存储和下载时间成本了。我一般会建议客户先下 14 级跑通流程,确认坐标系和拼接没问题,再按需补高层级。
2.3 并发控制与请求间隔
下载工具最怕两件事:一是请求太快被服务端限流,二是并发太高把本地带宽打满导致大量超时。常见做法是开一个线程池,线程数控制在 8 到 16 之间,每个请求之间加 50 到 100 毫秒的随机延迟。别小看这个延迟,它能显著降低被临时封禁的概率。
import requests import time import random from concurrent.futures import ThreadPoolExecutor, as_completed def download_tile(url, save_path, retry=3): """ 下载单张瓦片并保存到本地 retry: 失败重试次数 """ headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'http://map.baidu.com/' } for attempt in range(retry): try: # 随机延迟,降低被限流风险 time.sleep(random.uniform(0.05, 0.1)) resp = requests.get(url, headers=headers, timeout=10) if resp.status_code == 200 and len(resp.content) > 0: with open(save_path, 'wb') as f: f.write(resp.content) return True except Exception as e: if attempt == retry - 1: print(f"下载失败: {url}, 错误: {e}") return False def batch_download(tile_list, max_workers=12): """ 批量下载瓦片 tile_list: [(url, save_path), ...] """ success = 0 with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = {executor.submit(download_tile, url, path): (url, path) for url, path in tile_list} for future in as_completed(futures): if future.result(): success += 1 print(f"下载完成: {success}/{len(tile_list)}")download_tile里的Referer头建议带上,部分服务端会校验来源。timeout=10是单次请求超时,别设太长,否则卡住的线程会拖慢整体进度。batch_download的max_workers我一般设 12,家用宽带跑满没问题,公司网络如果有限速策略就降到 8。重试次数设 3 次足够,再多说明网络本身有问题,该查网络而不是加次数。
3. 从零跑通下载流程:环境准备与分步操作
3.1 环境依赖与目录结构规划
这个工具包解压后一般包含主程序、配置文件和一个tiles输出目录。运行环境需要 Python 3.7 以上,依赖requests和Pillow(部分工具用它做瓦片拼接预览)。安装命令就一行:
pip install requests Pillow目录结构建议提前规划好,别等下载完了再整理。我习惯按tiles/{z}/{x}/{y}.png的格式存,这是绝大多数地图引擎认的标准结构。如果后续要按区域打包,可以在外面再套一层区域名,比如tiles/beijing/14/1234/5678.png。配置文件里通常有这几个参数需要改:
| 参数名 | 含义 | 建议值 |
|---|---|---|
| min_zoom | 最小层级 | 10 |
| max_zoom | 最大层级 | 16 |
| bounds | 下载范围(经纬度) | 按实际区域填 |
| style | 瓦片样式 | pl(街道图) |
| thread_num | 并发线程数 | 12 |
| save_dir | 输出目录 | ./tiles |
bounds参数最容易填错。它一般接受[min_lng, min_lat, max_lng, max_lat]格式,注意顺序是经度在前、纬度在后,而且得是百度坐标系下的值。如果你手里是 GPS 坐标,先用在线工具转一下再填,否则下载的区域会整体偏移。
3.2 启动下载与进度监控
配置改好后,直接运行主程序:
python downloader.py --config config.ini有些工具包支持命令行参数覆盖配置,比如--zoom 14 --bounds "116.0,39.0,117.0,40.0",这样不用改文件就能临时跑一个小区域测试。第一次跑强烈建议先下 12 级、一个小矩形区域,确认瓦片能正常打开、位置没错,再放大范围。
进度监控方面,工具一般会在控制台打印已完成数量和速度。如果发现速度突然降到 0,大概率是被限流了,这时候别硬跑,停十分钟再继续。我一般会在脚本里加一个简单的速率检测:如果连续 20 个请求都失败,就自动暂停 60 秒再重试。
# 在 batch_download 中加入简单的限流检测 fail_count = 0 for future in as_completed(futures): if future.result(): success += 1 fail_count = 0 else: fail_count += 1 if fail_count >= 20: print("连续失败过多,暂停 60 秒...") time.sleep(60) fail_count = 0这段逻辑不复杂,但能省掉很多手动重启的麻烦。fail_count在成功时归零,连续失败到阈值就暂停,暂停后重新计数。
3.3 下载后的瓦片校验与拼接预览
下完之后别急着往项目里塞,先做两件事:一是随机抽几张瓦片用图片查看器打开,确认不是空白图或者错误提示图;二是用脚本拼一张大图看看整体效果。拼接脚本用 Pillow 就能写:
from PIL import Image import os def merge_tiles(tile_dir, zoom, x_start, x_end, y_start, y_end, output_path): """ 将指定范围的瓦片拼接成一张大图 tile_dir: 瓦片根目录 zoom: 层级 x_start/x_end/y_start/y_end: 瓦片行列号范围 """ tile_size = 256 width = (x_end - x_start + 1) * tile_size height = (y_end - y_start + 1) * tile_size canvas = Image.new('RGB', (width, height)) for x in range(x_start, x_end + 1): for y in range(y_start, y_end + 1): tile_path = os.path.join(tile_dir, str(zoom), str(x), f"{y}.png") if os.path.exists(tile_path): tile = Image.open(tile_path) canvas.paste(tile, ((x - x_start) * tile_size, (y - y_start) * tile_size)) canvas.save(output_path) print(f"拼接完成: {output_path}")merge_tiles里的x_start到x_end别选太大,否则内存扛不住。一般拼个 10x10 的瓦片区域就够检查了。如果拼出来发现某一块是空白,回去查那张瓦片的 URL 是不是负数行列号没处理对。
4. 避坑指南:瓦片下载中最容易翻车的五个地方
4.1 坐标系没转导致整体偏移
现象:下载的瓦片拼出来一看,位置整体偏了几百米到几公里,道路和实际地物对不上。原因:百度地图用的是 BD09 坐标系,而大多数 GPS 设备和 GeoJSON 数据是 WGS84。直接拿 WGS84 的经纬度去算瓦片行列号,必然偏。解决:在计算瓦片行列号之前,先做一次 WGS84 到 BD09 的转换。网上有现成的转换函数,核心就是两次偏移计算,别自己推导,直接用成熟代码。
4.2 负数行列号未处理导致边缘空白
现象:下载区域边缘的瓦片全是空白图,或者请求返回 404。原因:百度地图在低层级或者区域边缘会出现 x、y 为负的情况,直接拼 URL 服务端不认。解决:在拼 URL 之前判断 x、y 是否小于 0,小于 0 就加上2**z做偏移。这个逻辑在 2.1 节的代码里已经写了,直接抄就行。
4.3 并发过高被限流
现象:下载跑了几百张之后突然全部失败,等一会儿又能下几张,然后又失败。原因:请求频率超过了服务端的容忍阈值,触发了临时限流。解决:把线程数降到 8,每个请求加 50 到 100 毫秒随机延迟,再加一个连续失败自动暂停的逻辑。别想着用多 IP 绕,那是另一个层面的操作,而且不稳定。
4.4 存储路径过长导致写入失败
现象:下载到一半报错,提示找不到路径或者权限不足。原因:Windows 下路径长度限制是 260 个字符,tiles/16/12345/67890.png这种结构层级一多就容易超。解决:把输出目录设在盘符根目录下,比如D:/tiles,别放在桌面或者深层文件夹里。或者用短路径工具做映射。
4.5 瓦片样式参数写错导致下载的是错误图层
现象:下载的瓦片全是卫星图,但你要的是街道图。原因:URL 里的styles参数没改,默认用了sl。解决:街道图用pl,卫星图用sl,地形图用ter。改配置文件里的style参数,别直接改代码里的硬编码。
5. 进阶技巧:用瓦片下载工具做内网地图服务的完整链路
下载瓦片只是第一步,真正让它在内网跑起来,还得把瓦片挂到地图引擎上。我一般用 Leaflet 做前端,后端用 Nginx 做静态文件服务,整个链路很轻,不需要额外的地图服务器。
Nginx 配置就几行:
server { listen 8080; location /tiles/ { alias /data/tiles/; add_header Access-Control-Allow-Origin *; } }前端 Leaflet 里自定义瓦片图层:
L.tileLayer('http://内网IP:8080/tiles/{z}/{x}/{y}.png', { minZoom: 10, maxZoom: 16, attribution: '离线底图' }).addTo(map);这里有个细节:Leaflet 默认的瓦片 URL 模板是{z}/{x}/{y},和你下载的目录结构必须完全一致。如果你下载时用的是{z}/{y}/{x},前端就得改成对应的顺序,否则图会错乱。
另一个进阶用法是做瓦片合并,把相邻的瓦片拼成一张大图再切片,减少请求数。这个适合移动端或者弱网环境,但会增加预处理时间。我一般只在客户明确要求优化加载速度时才做,常规项目直接用原始瓦片就够了。
从那以后我每次下载完瓦片,都会先拼一张小图确认位置和样式,再挂到内网服务上跑一遍完整链路。这个习惯帮我省掉了至少三次返工。希望帮到你。
本文还有配套的精品资源,点击获取