经纬志
首页 / 实战手册 / 瓦片

512 像素的瓦片被按 256 排版,底图模糊又错位

先看结论

换了一份高清瓦片,地图反而变糊,还跟矢量数据差了半个屏幕。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

服务换成 512×512 的高清瓦片后,同一个缩放级别下:

  • 底图看起来更模糊,像是被压缩过。
  • 道路与矢量点位错开约 200 像素,越往地图边缘偏得越多。
  • 网络面板中每张瓦片的 naturalWidth 是 512,但 DOM 里渲染尺寸是 256×256。

原因

Leaflet 按 tileSize 计算「每级瓦片覆盖多少地图范围」。 512 像素的瓦片在 18 级覆盖的范围,相当于 256 像素瓦片在 17 级覆盖的范围, 即 zoomOffset 需要减 1 来补偿。

只把 tileSize 保持 256 时,Leaflet 认为每张瓦片代表 256 像素的地图范围, 于是把 512 像素的图片压到 256 显示,同时覆盖范围少了一半, 相邻瓦片位置全部错开。

瓦片尺寸tileSizezoomOffset结果
2562560正常
512512-1正常,清晰度提升
5122560模糊且错位
2565120瓦片被放大,模糊

排查过程

  1. 在网络面板选中一张瓦片,看「尺寸」列显示的固有尺寸是 256 还是 512。
  2. 看 DOM 里 .leaflet-tile 的 width 样式。
  3. 打开一张瓦片与矢量叠加处,量偏移距离是否等于瓦片宽度的一半。
  4. 检查是否同时开了 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, '渲染尺寸异常');

适用版本

库版本说明
Leaflet1.7 ~ 1.9tileSize 与 zoomOffset 需成对使用
OpenLayers9.xcreateXYZ({ tileSize }) 决定网格
Mapbox GL JS2.x / 3.x栅格源用 tileSize: 512
引用本文

经纬志(2026)。《512 像素的瓦片被按 256 排版,底图模糊又错位》。https://www.jingweizhi.com/cookbook/tile-512-retina.html(访问日期 2026-11-03)。