简介:百度地图瓦片下载工具是一款面向离线地图开发者与GIS学习者的实用工具,专门解决地图瓦片批量获取与离线数据准备难题。工具基于Web Mercator投影体系,内置百度坐标系与WGS84坐标转换机制,支持通过经纬度范围或行政区域划定下载范围,自由设定瓦片级别(级别越高细节越丰富),并能自动计算对应级别下的瓦片行列号,实现一键批量下载与离线整合。资源包共3个文件,以可执行jar程序为主,搭配properties参数配置文件与txt使用说明,便于快速调整坐标参数与下载策略,压缩包仅5.05MB,轻量易部署。目前已有5543人学习下载。借助该工具,开发者可高效制备离线导航底图、开展地理数据二次分析、构建自定义地图应用,减少对网络的依赖,广泛适用于离线导航、数据分析、教育科研与应急救援等场景,显著提升地图开发效率。
1. 为什么需要亲手处理一份百度地图瓦片下载工具
某公司接到一个内网 GIS 项目,客户明确要求地图底图不能依赖外网,必须在隔离环境里也能正常看。别人递过来一个压缩包,名字就叫百度地图瓦片下载工具.zip,解压后没有完整文档,只有一组脚本和说明。说白了,这类工具做的事情非常单一:把联网地图切成 256×256 的小瓦片,按缩放级别和行列号批量下载到本地,再拼成离线可用的底图。它不是地图平台软件,而是一组请求脚本加拼接脚本的组合包。适合 GIS 开发、测绘和地图应用集成场景的人来用;你不需要懂整个地图引擎,但必须先把投影、行列号和 URL 参数这三个概念搞明白,否则并发一高就 403、拼完图带缝、坐标偏移对不上项目数据,哪一个坑都够你白等一晚上。
2. 瓦片下载之前必须先搞清三件事:投影、行列号和 URL 规律
2.1 先看透瓦片 URL:参数列表比代码更重要
打开浏览器开发者工具,抓一次百度地图页面的图片请求,会看到类似这样的瓦片地址:
https://maponline0.bdimg.com/tile/?qt=tile&x=21420&y=1087&z=18&styles=pl&v=083&scaler=1&showtext=1第一次看到这一长串参数不要慌,下载脚本真正需要关心的只有几个关键项。我把它们整理成一张表,理解这张表,比照着网络教程抄代码更有用:
| 参数 | 含义 | 在下载工具中的用法 |
|---|---|---|
x、y、z | 瓦片行列号与缩放级别 | 任务列表的核心,z决定了该级别的瓦片总量 |
styles=pl | 普通街道矢量底图样式 | 换成styles=sl等可以切影像底图,但不同样式的层级覆盖范围不一样 |
scaler=1 | 清晰度倍率参数 | 部分版本用scaler=2表示高清瓦片,有的版本改成dpi相关参数,以实际抓包为准 |
showtext=1 | 是否叠加文字标注 | 离线底图若已叠加标注,业务图层反而容易冲突,可设 0 |
qt=tile | 瓦片接口标记 | 有的图层走qt=vec,请求地址结构不同,需要区分 |
v=083 | 瓦片版本号 | 会随线上迭代改变,不要写死,建议从页面加载请求中读取或做成配置项 |
另一个关键点是最前面的子域maponline0.bdimg.com。maponline0到maponline3四个子域都能返回同一张瓦片,轮换请求可以分摊单 IP 的压力。下载工具内置这个逻辑是基本要求;如果自己写的脚本固定打一个子域,遇到限流的概率会高很多。版本号变了、子域变了都不需要害怕,只要掌握了结构和含义,抓一次包就能适配。
2.2 经纬度转瓦片行列号:公式、代码和一个校准技巧
想要下载某个经纬度范围,第一步是把经纬度换算成瓦片行列号。百度瓦片基于 BD-09 加密坐标系,而业务项目里拿到的坐标通常是 WGS-84(GPS 设备的原始值)或 GCJ-02(国内图源常用的偏移坐标)。如果用 WGS-84 直接代入公式,偏差可能达到几十米,在瓦片级别上就是整整一格的位置错位。
我一般把整条转换链路分成三步:先 WGS-84 转 GCJ-02,再 GCJ-02 转 BD-09,最后 BD-09 坐标按 Web Mercator 投影转瓦片行列号。下面是可运行的 Python 实现:
import math def _transform_lat(lng: float, lat: float) -> float: ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat ret += 0.2 * math.sqrt(abs(lng)) ret += (20.0 * math.sin(6.0 * lng * math.pi) + 20.0 * math.sin(2.0 * lng * math.pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(lat * math.pi) + 40.0 * math.sin(lat / 3.0 * math.pi)) * 2.0 / 3.0 ret += (160.0 * math.sin(lat / 12.0 * math.pi) + 320.0 * math.sin(lat * math.pi / 30.0)) * 2.0 / 3.0 return ret def _transform_lng(lng: float, lat: float) -> float: ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(abs(lng)) ret += (20.0 * math.sin(6.0 * lng * math.pi) + 20.0 * math.sin(2.0 * lng * math.pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(lng * math.pi) + 40.0 * math.sin(lng / 3.0 * math.pi)) * 2.0 / 3.0 ret += (150.0 * math.sin(lng / 12.0 * math.pi) + 300.0 * math.sin(lng / 30.0 * math.pi)) * 2.0 / 3.0 return ret def wgs84_to_gcj02(lng: float, lat: float) -> tuple: """WGS-84 坐标转 GCJ-02 坐标,这是国内图源最常见的坐标转换链路""" if lng < 72.004 or lng > 137.8347 or lat < 0.8293 or lat > 55.8271: return lng, lat d_lng = _transform_lng(lng - 105.0, lat - 35.0) d_lat = _transform_lat(lng - 105.0, lat - 35.0) rad_lat = lat / 180.0 * math.pi magic = math.sin(rad_lat) magic = 1 - 0.006693421622965943 * magic * magic sqrt_magic = math.sqrt(magic) d_lat = (d_lat * 180.0) / ((6378245.0 * (1 - 0.006693421622965943)) / (magic * sqrt_magic) * math.pi) d_lng = (d_lng * 180.0) / (6378245.0 / sqrt_magic * math.cos(rad_lat) * math.pi) return lng + d_lng, lat + d_lat def gcj02_to_bd09(lng: float, lat: float) -> tuple: """GCJ-02 坐标转 BD-09 坐标,百度地图使用的加密坐标系""" x_pi = math.pi * 3000.0 / 180.0 z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi) theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi) bd_lng = z * math.cos(theta) + 0.0065 bd_lat = z * math.sin(theta) + 0.006 return bd_lng, bd_lat def gcj02_to_tile(lng: float, lat: float, zoom: int) -> tuple: """经纬度转百度瓦片行列号;输入 GCJ-02 坐标,输出 (x, y)""" bd_lng, bd_lat = gcj02_to_bd09(lng, lat) n = 1 << zoom x = (bd_lng + 180.0) / 360.0 * n y = (1.0 - math.asinh(math.tan(math.radians(bd_lat))) / math.pi) / 2.0 * n return int(x), int(y)这段代码的逻辑是从 GPS 原始坐标一路转到百度投影坐标,最后按标准 Web Mercator 投影换算成瓦片行列号。n = 1 << zoom表示当前缩放级别下单边瓦片总数,zoom每加 1,瓦片数量翻四倍。math.asinh(math.tan(lat_rad))是 Web Mercator 的等价写法,Python 3 自带,不用自己再拼对数表达式。
这条链路里最容易踩坑的地方是 y 轴方向。部分版本的百度瓦片服务把 y 轴定义成从南到北,和 Web Mercator 常见实现里的从北到南相反。如果拼接时发现上下颠倒,或者每一行瓦片错位一格,用n - 1 - y翻转一次再试。代码写完之后不要直接信任结果,选一个熟悉的交叉路口,在浏览器开发者工具里找到页面实际请求的瓦片 URL,记录(x, y, z),再用这个交叉路口的经纬度跑一遍函数,对不上就翻转重试。这一步校准不花多少时间,却能省下后面拼接阶段的几小时排错。
2.3 范围转任务列表:小脚本生成全部待下载瓦片
坐标转换函数搞定之后,下一步是把矩形经纬度范围展开成待下载的瓦片任务列表。这里有个容易迷糊的细节:经纬度范围要按“左上角”和“右下角”去传参,而不是简单的最小最大组合。
def build_tile_tasks(lng_left, lat_top, lng_right, lat_bottom, zoom_list): """按左上角和右下角经纬度生成瓦片任务列表""" tasks = [] for z in zoom_list: n = 1 << z x1, y1 = gcj02_to_tile(lng_left, lat_top, z) # 左上角瓦片 x2, y2 = gcj02_to_tile(lng_right, lat_bottom, z) # 右下角瓦片 if x1 > x2: x1, x2 = x2, x1 if y1 > y2: y1, y2 = y2, y1 # 防止经纬度越界导致行列号超出合法区间 x1 = max(0, min(n - 1, x1)) x2 = max(0, min(n - 1, x2)) y1 = max(0, min(n - 1, y1)) y2 = max(0, min(n - 1, y2)) for x in range(x1, x2 + 1): for y in range(y1, y2 + 1): tasks.append((z, x, y)) return tasks参数说明:lng_left, lat_top是目标区域西北角的坐标,lng_right, lat_bottom是东南角的坐标。gcj02_to_tile在内部把经纬度转成行列号,所以这里反着传纬度就能拿到矩形包络的边界。边界越界判断不是多余的:如果范围跨过 180° 经线,或者经纬度来自境外区域,行列号可能跑到合法区间外面,请求会一直 404。
生成任务后,先打印任务数,不要直接开跑:
tasks = build_tile_tasks( 116.30, 40.00, # 左上角:经度 116.30,纬度 40.00 116.60, 39.80, # 右下角:经度 116.60,纬度 39.80 [12, 13, 14, 15] ) print(f"total: {len(tasks)}")这个范围大约 0.3 度经度、0.2 度纬度,12 到 15 级加起来是几张到几万张不等的量级。瓦片数量估算直接用(x2 - x1 + 1) * (y2 - y1 + 1)逐级相加,单张按 20 KB 到 60 KB 粗算,就能知道磁盘和带宽的预算。很多人在这步会犯一个低级错误:级别列表填了 1 到 19,结果任务数跑到千万级,下载工具再稳也扛不住。建议先按业务实际需求选层级,比如内网底图通常 3 到 16 级够用,局部重点区域再补 17 到 18 级。
3. 把瓦片拼成一张底图:Python 拼接与坐标系对齐
3.1 下载完成后先做完整性检查
任务跑完不等同于文件都能用。我见过太多次“下载完成率 100%,拼图时黑一块白一块”的情况。所以下载结束后先扫一遍目录,把可疑文件找出来:
from pathlib import Path from collections import Counter root = Path("tiles") total = 0 suspicious = [] level_counter = Counter() for p in root.rglob("*.png"): total += 1 level_counter[p.parent.name] += 1 size = p.stat().st_size if size < 512: suspicious.append(str(p)) print("total:", total) print("levels:", dict(level_counter)) print("suspicious:", len(suspicious))正常 256×256 瓦片经过压缩后通常也有几 KB,小于 512 字节的极大概率是异常响应。level_counter能快速看出某个缩放级别的瓦片数量有没有整片缺失,比人工看文件列表高效得多。
完整性检查通过后,再随机抽几张瓦片用图片查看器打开,确认内容不是纯白块或者重复的占位图。有的下载工具会为了拼接好看,主动把缺失瓦片填成同类颜色,这类文件大小正常但内容无效,文件扫描发现不了,只能靠人眼抽检。
3.2 用 Pillow 按坐标粘贴,顺序别搞反
检查无误后进入拼接环节。核心逻辑是把瓦片行列号折算成像素坐标,再用 Pillow 逐张粘贴:
from PIL import Image def merge_tiles(z: int, x_range, y_range, root=Path("tiles")): x1, x2 = x_range y1, y2 = y_range width = (x2 - x1 + 1) * 256 height = (y2 - y1 + 1) * 256 merged = Image.new("RGB", (width, height)) for x in range(x1, x2 + 1): for y in range(y1, y2 + 1): # 按目录结构 z/x/y.png 定位瓦片 tile_path = root / str(z) / str(x) / f"{y}.png" if not tile_path.exists(): continue img = Image.open(tile_path) px = (x - x1) * 256 py = (y - y1) * 256 merged.paste(img, (px, py)) img.close() return merged merged = merge_tiles(15, (21420, 21540), (1080, 1110)) merged.save("merged_z15.png")这里的像素偏移计算依赖一个前提:y增大方向必须是大图从上到下。如果之前校准公式时没有翻转 y,拼出来的图会上下颠倒或按行错位,排查成本很高。另一个容易忽略的点是目录结构,有的下载工具按z/y/x.png保存,有的是z/x/y.png,代码里的定位路径要和工具实际输出保持一致。
拼大图会暴露出内存问题。100×100 张瓦片意味着 25600×25600 像素的 RGB 图像,内存占用接近 2 GB。真到这个规模,我不建议继续用 Pillow 拼单张。更通用的做法是保留z/x/y.png目录结构,直接把整个目录交给地图组件加载;或者用 GDAL 的gdal_merge.py走命令行,它支持流式写入,对大图更友好。
3.3 拼接后的坐标系偏移怎么处理
拼好的底图放到业务系统里,偏移问题迟早会遇到。最典型的现象是:瓦片底图看着正常,但叠加项目矢量边界时,整体向某个方向偏移几米到几十米。
原因是坐标系不一致。百度瓦片底图使用 BD-09 加密坐标系,项目数据如果来自 GPS 原始坐标,是 WGS-84,中间还隔着 GCJ-02 的偏移。瓦片是位图,拼接再完整,坐标系不对也是废的。常见的处理方式有两种:一是把业务数据转换到 BD-09 坐标系再叠加;二是底图换用标准坐标系的图源。前者在项目层面改动小,我一般优先推荐。
转换逻辑可以直接复用 2.2 节的坐标代码,但要写清楚数据链路。项目里曾有人把 WGS-84 的 GPS 轨迹直接叠在百度底图上,验收时发现所有轨迹都落在路的左侧,最后定位到是坐标系没转换。这类问题不算代码 bug,而是数据规范问题。建议在下载工具的说明文件里注明“瓦片坐标系为 BD-09,业务数据如需叠加请先做坐标转换”,这句话能省掉很多跨团队扯皮。
4. 百度地图瓦片下载避坑:403、乱码、偏缝与缺图
4.1 下着下着 HTTP 403,回调全线失败
现象:任务跑到 60% 时开始连续返回 403,停十分钟再跑又恢复,然后再次 403。
原因:请求频率达到了瓦片服务的限流阈值。瓦片服务不会公开具体 QPS 限制,但实际经验是单个 IP 固定子域、并发超过 32 时很容易触发。403 是服务端明确拒绝,不是网络抖动,重试太频繁反而会被限制更久。
解决:把并发数降到 8 到 16,四个子域随机轮换,请求头补全浏览器标识。可以单独写个状态码统计,判断当前是被限流还是服务端异常:
from collections import Counter status_counter = Counter() def download_one(z, x, y): host = random.choice(SUBDOMAINS) url = f"https://{host}/tile/?qt=tile&x={x}&y={y}&z={z}&styles=pl&scaler=1&showtext=1" resp = session.get(url, timeout=(5, 10)) status_counter[resp.status_code] += 1 if resp.status_code == 403: # 被限流时立刻退避,不要重试 raise RuntimeError("limited")403 出现后不要立刻重试,用指数退避,从 1 秒开始翻倍,最多等 20 秒。这不是玄学,限流策略一般会按时间窗口计数,你等得越久,恢复概率越大。
4.2 瓦片下载下来打不开,只有几字节
现象:下载出来的.png只有 1 KB 左右,图片查看器直接报文件损坏。
原因:两种常见可能。第一种是脚本以stream=True方式读响应,但请求头带了Accept-Encoding: gzip,响应返回的是压缩流,没有解压就被写进文件;第二种是服务器返回了一个 JSON 错误提示,内容也被存成了.png。
解决:先看文件头再决定处理方式。PNG 文件头是\x89PNG,GZIP 压缩流是\x1f\x8b,JSON 文本则以{开头:
with open("bad_tile.png", "rb") as f: head = f.read(4) print(head)如果是 gzip 问题,请求头显式写Accept-Encoding: identity,让服务器返回原始未压缩内容;如果是 JSON 问题,说明请求被拦截,大概率是并发太高或缺少 Referer。用requests时,直接读resp.content会自动处理 gzip,乱码问题多半出在手动操作底层流,这一点排查起来很快。
4.3 拼出来的图有横竖缝,像百叶窗
现象:大图每一块瓦片边缘都有细缝,瓦片之间错位或者露出背景色,整体看起来像百叶窗。
原因:最常见的是 y 轴方向搞反,或者行列号取整边界差一格。之前提过,百度瓦片的 y 轴方向在不同版本、不同图层上可能有差异,代码里写死某个方向,换一个区域或版本就会翻车。也有少数情况是瓦片本身带了重叠边框,百度官方瓦片没有这个问题,第三方图源偶尔会出现。
解决:回到 2.2 节的校准方法,用目标区域中心的一张已知瓦片做验证。如果百叶窗只在上下方向出现,翻转 y 后重新拼接;如果缝隙是固定间隔出现,就要检查任务生成时是哪个循环出了问题。我调试过三次这类问题,两次是公式翻转,一次是旧的瓦片目录复用到新任务,新旧级别混存导致读取顺序混乱。
4.4 某几级瓦片大面积 404,不是网络问题
现象:18 级能正常下载,19 级大部分返回 404,中间夹杂少量正常图片。
原因:瓦片服务对部分区域或部分图层只生产到某个最高级别。街道样式和影像样式的层级覆盖也不一样,不能只看 URL 格式相同就认为所有级别一定都有图。
解决:用中心点探测可用的最高级别,避免把整个范围的 404 当成网络故障:
def probe_max_zoom(center_lng, center_lat, zoom=19): while zoom > 3: x, y = gcj02_to_tile(center_lng, center_lat, zoom) resp = session.get(make_url(x, y, zoom), timeout=5) if resp.status_code == 200 and len(resp.content) > 1000: return zoom zoom -= 1 return 3探测点选目标区域核心位置,不要选边缘。如果 19 级在边缘区域是真的没有,而在核心区域有,说明该级别覆盖不完整,任务生成时应该对每个瓦片都做存在性检查。把 404 瓦片记录到missing.txt,最后统一用上一级瓦片放大补上,而不是在下载过程中反复重试。
5. 把下载脚本改成工程工具:并发限速、断点续传与参数调优
5.1 线程池 + 子域轮换,让速度接近你带宽的上限
单线程 for 循环下载几千张瓦片确实慢,耗时主要集中在等待响应上。用线程池是最直接的提速方式,关键在控制并发数和限速:
import random import time import threading from concurrent.futures import ThreadPoolExecutor, as_completed SUBDOMAINS = [ "maponline0.bdimg.com", "maponline1.bdimg.com", "maponline2.bdimg.com", "maponline3.bdimg.com", ] headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Referer": "https://map.baidu.com/", "Accept": "image/webp,image/apng,image/*,*/*;q=0.8", "Accept-Encoding": "identity", } session = requests.Session() session.headers.update(headers) rate_lock = threading.Lock() last_request_time = [0.0] def rate_limit(interval=0.05): """简单限速:控制全局请求间隔""" with rate_lock: now = time.time() wait = interval - (now - last_request_time[0]) if wait > 0: time.sleep(wait) last_request_time[0] = time.time() def download_one(task): z, x, y = task host = random.choice(SUBDOMAINS) url = f"https://{host}/tile/?qt=tile&x={x}&y={y}&z={z}&styles=pl&scaler=1&showtext=1" tmp = Path(f"tiles/{z}/{x}/{y}.tmp") final = Path(f"tiles/{z}/{x}/{y}.png") tmp.parent.mkdir(parents=True, exist_ok=True) try: rate_limit(0.05) resp = session.get(url, timeout=(5, 10)) if resp.status_code == 200 and resp.content.startswith(b"\x89PNG"): tmp.write_bytes(resp.content) tmp.rename(final) return True except requests.RequestException: pass return False with ThreadPoolExecutor(max_workers=8) as pool: futures = [pool.submit(download_one, t) for t in tasks] for fut in as_completed(futures): fut.result()逻辑说明:rate_limit(0.05)控制全局请求间隔大约 50 毫秒,等于每秒并发上限 20 次。即使线程池开了 8 个线程,实际请求频率也被限速器锁住,这比单纯控制max_workers更稳妥。限速器用全局锁实现,避免多线程同时改写last_request_time造成限流失效。
参数说明:max_workers=8是保守值。如果你的网络带宽高且瓦片服务没限流,可以调到 16;但一旦出现 403,优先减少并发而不是增加重试。timeout=(5, 10)表示连接超时 5 秒、读取超时 10 秒,瓦片单张只有几十 KB,超过这个时间还没返回,基本是网络或服务端问题,卡住线程纯属浪费。
5.2 断点续传:记录已完成任务,别一断就重来
大范围下载跑几个小时很容易断,没有断点续传的工具在我这里直接被判不合格。常见的实现是把已完成任务追加到一个文本文件里:
def load_done(done_file="done.txt"): done = set() if Path(done_file).exists(): for line in Path(done_file).read_text().splitlines(): if line: z, x, y = line.split(",") done.add((int(z), int(x), int(y))) return done def mark_done(done_file, z, x, y): with open(done_file, "a") as f: f.write(f"{z},{x},{y}\n")下载函数里成功后调用mark_done,任务循环前先load_done并跳过已完成的项。这里有一个重要细节:不要只用“文件是否存在”判断完成状态,上次中断可能留下 0 字节文件。所以下载时先写.tmp文件,再原子重命名为正式文件,随后写入done.txt。这样即使程序在中途崩溃,也不会留下“文件存在但内容无效”的脏状态。
断点续传对服务器压力也有好处。重新跑任务时,已完成区域一秒钟内就能跳过,不会重复请求已经下载过的瓦片,这在服务端限流场景下非常重要。
5.3 三个必调参数:并发数、超时时间与重试间隔
不同的网络环境对下载工具参数的要求完全不同,最值得花时间调的是下面三个:
| 参数 | 推荐范围 | 判断依据 |
|---|---|---|
| 并发数 | 8 到 16 | 超过 32 容易触发 403;低于 4 则带宽利用不起来 |
| 连接超时 | 3 到 5 秒 | 公网环境建议 5 秒,内网或代理可以缩短到 3 秒 |
| 读取超时 | 10 到 15 秒 | 瓦片单张很小,超过 20 秒基本是连接被卡住 |
| 重试次数 | 3 次 | 第一次失败后可立即重试,第二次等待 1 秒,第三次等待 4 秒 |
调参有个反直觉的点:超时时间不合适时,盲目加长会让线程被慢请求占住,后续任务排队,整体速度反而下降。正确做法是观察日志里的错误分布。如果超时频繁,先降低并发数,再看是连接超时还是读取超时。如果 403 频繁,调大重试间隔而不是并发数。这些参数最好做成配置项,放在脚本文件头部或一个config.py里,不要写死在业务代码中间,换一台服务器跑的时候你就知道这个习惯多重要了。
6. 下载前先算总账:瓦片数量估算与按需加载的验证套路
下载任务生成后,第一件事永远是按行列号差算出瓦片数量,再乘单张体积估算磁盘占用。一个城市的 3 到 16 级全量下载,数据量能达到几十 GB;如果把 17 到 19 级也带上,那将是更大的数字。很多下载工具跑了一整天,最后发现需要的只是其中一部分层级。我现在无论接什么需求,都会先拉一个小范围、挑几个关键层级做样张,验证清晰度和覆盖范围,再决定要不要全量下载。
验证样张有个非常实用的套路:把下载好的瓦片按z/x/y.png目录放好,直接用 Python 起一个本地静态服务:
python -m http.server 8080 --directory tiles然后在浏览器里打开地图调试页面,把瓦片地址指向本地服务对应的{z}/{x}/{y}.png。对比在线瓦片和本地瓦片在同一位置是否完全重合。这个方法能同时验证坐标换算、y 轴方向和瓦片完整性,比直接拼接大图再叠加数据要直观得多。如果黑屏,优先检查目录结构;如果位置偏移,回到坐标转换链路逐段排查;如果边缘缺图,多半是层级覆盖范围问题。
这里我要说一个真实的教训:有一回我贪心,选择了全城 1 到 19 级全量下载,连续跑了三个晚上,最后因为 1 到 10 级的通用瓦片对业务根本没价值,删掉了近八成的数据。从那以后,我养成了一个习惯:不管工具多好用,任务列表生成后先打印总数和磁盘预估,把层级按业务需求拆分批次,跑完一批验证一批,再决定是否继续。这个习惯帮我避开过很多次“下载一时爽,清理火葬场”的局面。希望帮到你。
本文还有配套的精品资源,点击获取