经纬志
首页 / Leaflet / 教程

瓦片图层与底图切换

先看结论

底图是地图的第一层内容,Leaflet 把它抽象成 TileLayer: 给出一个带占位符的 URL 模板,剩下的按视野计算、缓存、拼接都由图层完成。

最后验证 2026-11-03 · Leaflet 1.9.4 · 更新 2026-10-27 · 引用本文

瓦片金字塔与 z/x/y

瓦片服务把世界地图按缩放级别切成正方形图片,第 0 级是一张 256×256 的图, 每升一级在横纵两个方向各切一刀,瓦片数量变成四倍。地址里的三个占位符因此有明确含义:

占位符含义取值范围
{z}缩放级别一般为 0–19
{x}列号,从西经 180° 起算0 到 2z−1
{y}行号,从北纬 85.05° 起算0 到 2z−1
{s}子域名,用于突破浏览器并发限制由 subdomains 指定
{r}高清屏标记普通屏为空串,Retina 屏为 @2x

{y} 的方向是这套体系里最容易出错的地方。Web 墨卡托瓦片从北向南编号, 而 TMS 规范从南向北编号,两者正好上下翻转。接入只支持 TMS 的服务时, 在图层参数里加 tms: true,Leaflet 会自动换算,不需要自己拼地址。

TileLayer 的常用参数

参数默认说明
minZoom / maxZoom0 / 18该图层自身可用的级别,与地图的缩放范围是两回事
subdomains'abc'替换 {s} 的字符集合
tileSize256瓦片边长,高清瓦片常为 512
zoomOffset0请求级别与地图级别的差值,512 瓦片通常设 −1
detectRetinafalse高清屏时自动把 tileSize 减半并提升请求级别
noWrapfalse关闭东西方向无限重复,缩到很小的时候不会出现多份世界
bounds—只在该矩形范围内请求瓦片,配合 noWrap 限制服务范围
errorTileUrl''单张瓦片失败时替换成的图片地址
crossOriginfalse需要把瓦片画进 canvas 或做像素处理时必须打开
keepBuffer2视野外多保留几圈瓦片,拖动回来时不用重新请求
updateWhenIdle移动端 true拖动结束后才加载新瓦片,省流量但会短暂留白
opacity1图层透明度,叠两层底图做对比时有用
className''加到瓦片容器上的类名,可用 CSS 滤镜改色

切换底图

标准做法是把所有底图作为 baseLayers 交给 L.control.layers, 控件会自动保证同一时间只有一层可见,并同步右下角的版权信息:

var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
  maxZoom: 19,
  attribution: '© OpenStreetMap contributors'
});

var cartoLight = L.tileLayer(
  'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
    subdomains: 'abcd',
    maxZoom: 20,
    attribution: '© OpenStreetMap contributors © CARTO'
  }
);

var map = L.map('map', {
  center: [39.907359, 116.391263],
  zoom: 11,
  layers: [osm]
});

L.control.layers({
  'OpenStreetMap': osm,
  'Carto 浅色': cartoLight
}, null, { position: 'topright' }).addTo(map);

自己写按钮切换时要多做两件事:移除旧图层、确认新图层的 maxZoom 不小于当前地图级别。 只 addLayer 不移除,旧底图会留在下面继续占用请求,而且版权信息会叠成一串。

国内瓦片与坐标偏移

天地图、高德、腾讯都提供 XYZ 瓦片地址,替换的只是 URL 模板。差别集中在两处:

  • 天地图需要 tk 参数,影像层用 img_w(经纬度投影)或 img_c(墨卡托), 图层代码不同,不能混用。
  • 高德、腾讯使用 GCJ-02 坐标。把 WGS84 的 GPS 点直接标上去,整体会偏移几百米, 需要在数据侧做坐标转换,而不是调整瓦片地址。

先确认底图坐标系,再决定要不要转换。同一份业务数据通常只能配一套底图。 底图换了、点没换,偏移就会立刻出现。

瓦片加载失败怎么查

底图发灰、出现空白方格,说明瓦片请求没有成功。监听 tileerror 统计失败次数, 比盯着控制台更直观:

var failed = 0;
tiles.on('tileerror', function (e) {
  failed++;
  if (failed === 3) {
    console.warn('瓦片连续失败,检查网络或服务地址', e.tile.src);
  }
});

map.on('baselayerchange', function (e) {
  failed = 0;
  console.log('当前底图', e.name);
});

常见原因按出现频率排列:

  • 模板里的 {s} 没有对应 subdomains,请求到了不存在的子域。
  • 服务要求 Referer 白名单或 Key,本地 file:// 打开时被拒绝。
  • 当前缩放级别超过图层 maxZoom,没有瓦片可请求。
  • 跨域图片被浏览器拦截,多见于需要读取像素做滤镜的场景。

性能与体验

keepBuffer 决定视野外缓存几圈瓦片。调到 4 以上,快速拖动回原位时几乎不闪白, 代价是内存里多留几十张图。移动端默认 updateWhenIdle: true, 拖动过程中不发请求,松手才加载;桌面端保持 false,拖动时瓦片连续补齐。

叠加多层瓦片(底图 + 注记 + 专题图)时,给上层设置 zIndex, 不要依赖添加顺序。高缩放级别下瓦片数量增长很快,把地图 maxZoom 设成实际需要的值, 能避免用户缩到 22 级看到一片空白。

引用本文

经纬志(2026)。《瓦片图层与底图切换》。https://www.jingweizhi.com/leaflet/tutorial/tile-layer.html(访问日期 2026-11-03)。