瓦片目录从 1 级开始,地图整体放大了一倍
离线包只有 1~19 级目录,前端按 0 级起算,画面始终比实际大一号,19 级还缺图。
现象
用 MOBAC 导出的离线瓦片,目录结构是 1/0/0.png 到 19/...png,
没有 0 级。
前端配置 minZoom: 0, maxZoom: 18 后:
- 地图初始级别显示的是 1 级的画面,但控件显示 zoom 0。
- 缩放到最大时出现灰块,网络面板请求
18/x/y.png实际全部命中 19 级目录之外的空白。 - 叠加的矢量数据比底图小一半。
原因
瓦片层级只是一个目录名,切图工具默认从 1 级开始(1 级全球 2×2 张瓦片),
而 Leaflet 的 zoom 从 0 开始,0 级全球 1×1 张。
两者差 1,导致请求的 {z} 比目录小 1:
Leaflet 的 0 级去请求 0/ 目录,不存在;
实际能显示的是 Leaflet 1 级对应的 1/ 目录,于是一直错位。
排查过程
- 列出瓦片根目录,看最小的层级目录号是 0 还是 1。
- 在
tileload事件里打印e.coords,与目录号对比。 - 看最大层级目录号,与
maxZoom配置比较。 - 检查
minNativeZoom/maxNativeZoom是否设置,它们不影响请求层级但影响放大行为。
map.on('tileload', e => console.log(e.coords, e.tile.src));
// { x: 0, y: 0, z: 1 } -> 实际请求 /tiles/1/0/0.png
// 目录结构
// tiles/1/0/0.png
// tiles/2/0/0.png ... tiles/2/3/3.png (2 级 4×4)
// tiles/19/...
解决方案
用 zoomOffset 把前端层级平移到目录层级。
zoomOffset: -1 表示请求时 z = 前端 zoom + (-1),
但更直观的做法是让 minZoom 从 1 开始并设 zoomOffset: 0,两者择一。
// 目录从 1 级起,前端也从 1 级起:直接对齐
L.tileLayer('/tiles/{z}/{x}/{y}.png', {
minZoom: 1,
maxZoom: 19,
tileSize: 256
}).addTo(map);
// 或者保持前端 0 级起,用 zoomOffset 补偿
L.tileLayer('/tiles/{z}/{x}/{y}.png', {
minZoom: 0,
maxZoom: 18,
zoomOffset: 1, // 请求 z = zoom + 1
tileSize: 256
}).addTo(map);
maxNativeZoom 与 maxZoom 不是一回事。
目录只到 19 级时,把 maxNativeZoom 设为 19、maxZoom 设为 21,
Leaflet 会把 19 级瓦片放大复用,不会请求不存在的层级。
验证
tileload 打印的 z 与请求目录号一致;
缩放到最大级别仍能出图(复用 19 级瓦片),不再出现灰块;
叠加矢量数据与底图对齐。
console.assert(e.coords.z === Number(e.tile.src.match(/\/(\d+)\//)[1]), '层级仍未对齐');
map.setZoom(21); // 不应出现 404
适用版本
| 库 / 工具 | 版本 | 说明 |
|---|---|---|
| Leaflet | 1.7 ~ 1.9 | zoomOffset 与 maxNativeZoom 均可用 |
| OpenLayers | 9.x | 用 createXYZ({ minZoom, maxZoom }) 显式对齐 |
| MOBAC | 2.x | 默认最小层级为 1 |
经纬志(2026)。《瓦片目录从 1 级开始,地图整体放大了一倍》。https://www.jingweizhi.com/cookbook/tile-z-offset.html(访问日期 2026-11-03)。