512 像素的瓦片被按 256 排版,底图模糊又错位
换了一份高清瓦片,地图反而变糊,还跟矢量数据差了半个屏幕。
现象
服务换成 512×512 的高清瓦片后,同一个缩放级别下:
- 底图看起来更模糊,像是被压缩过。
- 道路与矢量点位错开约 200 像素,越往地图边缘偏得越多。
- 网络面板中每张瓦片的
naturalWidth是 512,但 DOM 里渲染尺寸是 256×256。
原因
Leaflet 按 tileSize 计算「每级瓦片覆盖多少地图范围」。
512 像素的瓦片在 18 级覆盖的范围,相当于 256 像素瓦片在 17 级覆盖的范围,
即 zoomOffset 需要减 1 来补偿。
只把 tileSize 保持 256 时,Leaflet 认为每张瓦片代表 256 像素的地图范围,
于是把 512 像素的图片压到 256 显示,同时覆盖范围少了一半,
相邻瓦片位置全部错开。
| 瓦片尺寸 | tileSize | zoomOffset | 结果 |
|---|---|---|---|
| 256 | 256 | 0 | 正常 |
| 512 | 512 | -1 | 正常,清晰度提升 |
| 512 | 256 | 0 | 模糊且错位 |
| 256 | 512 | 0 | 瓦片被放大,模糊 |
排查过程
- 在网络面板选中一张瓦片,看「尺寸」列显示的固有尺寸是 256 还是 512。
- 看 DOM 里
.leaflet-tile的width样式。 - 打开一张瓦片与矢量叠加处,量偏移距离是否等于瓦片宽度的一半。
- 检查是否同时开了
detectRetina,它会隐式改变tileSize的计算。
const img = document.querySelector('.leaflet-tile');
console.log(img.naturalWidth, img.clientWidth); // 512 256 <- 尺寸不匹配
解决方案
tileSize 与 zoomOffset 必须成对设置。
// 512 像素瓦片的标准配置
L.tileLayer('https://tile.example.com/{z}/{x}/{y}.png', {
tileSize: 512,
zoomOffset: -1,
minZoom: 1,
maxZoom: 19,
detectRetina: false // 已经用 512 了,不要再开
}).addTo(map);
// OpenLayers:显式给出 tileGrid,避免用默认 256
new TileLayer({
source: new XYZ({
url: 'https://tile.example.com/{z}/{x}/{y}.png',
tileSize: 512,
projection: 'EPSG:3857',
tileGrid: createXYZ({ tileSize: 512, maxZoom: 19 })
})
});
detectRetina 与 512 瓦片不要同时用。
开启后 Leaflet 会自动请求 z+1 并把 tileSize 减半,
再叠加 zoomOffset: -1 就会互相抵消,出现跳级。
验证
DOM 中瓦片的 clientWidth 与 naturalWidth 都为 512,
或按 CSS 缩放为 256 但清晰度来自 512 源;
矢量点位与底图道路重合,偏移小于 2 像素。
console.assert(img.naturalWidth === 512, '瓦片源尺寸不对');
console.assert(Math.abs(img.clientWidth / img.naturalWidth - 1) < 0.01 ||
img.clientWidth === 256, '渲染尺寸异常');
适用版本
| 库 | 版本 | 说明 |
|---|---|---|
| Leaflet | 1.7 ~ 1.9 | tileSize 与 zoomOffset 需成对使用 |
| OpenLayers | 9.x | createXYZ({ tileSize }) 决定网格 |
| Mapbox GL JS | 2.x / 3.x | 栅格源用 tileSize: 512 |
经纬志(2026)。《512 像素的瓦片被按 256 排版,底图模糊又错位》。https://www.jingweizhi.com/cookbook/tile-512-retina.html(访问日期 2026-11-03)。