经纬志
首页 / deck.gl / 教程

与底图结合:瓦片、投影与偏移

先看结论

deck.gl 不提供底图,只提供坐标系。要让数据落在地图上, 要么自己加载瓦片,要么把它挂到已有的地图库里。 两条路都绕不开 Web Mercator 和坐标偏移。

最后验证 2026-11-03 · deck.gl 9.0 · 更新 2026-10-27 · 引用本文

三种结合方式

方式做法适用
自带瓦片 TileLayer + BitmapLayer 叠在数据图层下面 只需要静态底图,不想引入第二个地图库
交给地图库 MapLibre / Mapbox 提供底图,deck.gl 用 MapboxOverlay 叠加 需要底图交互、注记、图层控制
与二维地图联动 Leaflet / OpenLayers 管底图,deck.gl 作为同步画布覆盖其上 已有地图应用,增量引入 GPU 图层

三种方式的数据坐标都是 [经度, 纬度],区别在于谁负责把视角变化 通知给 deck.gl。选错方式会导致拖动底图时数据不跟随,这是集成阶段最常见的问题。

用 TileLayer 加载瓦片

TileLayer 是复合图层:它按当前视角计算需要的瓦片, 为每块瓦片生成一个子图层。子图层通常用 BitmapLayer, 通过 renderSubLayers 拿到瓦片图像和地理范围。

var basemap = new deck.TileLayer({
  id: 'basemap',
  data: 'https://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}',
  minZoom: 0,
  maxZoom: 16,
  tileSize: 256,
  renderSubLayers: function (props) {
    var bb = props.tile.boundingBox;   // [[西, 南], [东, 北]]
    return new deck.BitmapLayer(props, {
      data: null,
      image: props.data,
      bounds: [bb[0][0], bb[0][1], bb[1][0], bb[1][1]]
    });
  }
});

var deckgl = new deck.Deck({
  canvas: 'cv',
  width: '100%',
  height: '100%',
  initialViewState: { longitude: 116.39, latitude: 39.9, zoom: 9, pitch: 0 },
  controller: true,
  layers: [basemap, dataLayer]      // 先底图,后数据
});

{z}、{x}、{y} 会被替换为层级与瓦片行列号,顺序由瓦片服务决定。 XYZ 与 TMS 的 y 轴方向相反,拿到 TMS 服务时要写成 {z}/{x}/{-y}(部分版本支持反向索引),否则瓦片上下颠倒甚至 404。

瓦片服务的坐标系陷阱

绝大多数在线瓦片是 Web Mercator(EPSG:3857),经纬度直接对应 [lng, lat],无需换算。国内部分服务商(高德、腾讯)使用 GCJ-02 加密坐标,与 GPS 的 WGS-84 之间存在几十到上百米的偏移。

数据来源坐标直接叠加的后果
天地图、OSM、EsriWGS-84 / CGCS2000对齐正常
高德、腾讯瓦片GCJ-02GPS 点整体偏移数百米
百度瓦片BD-09偏移更大,且投影参数不同

处理方式只有两条:把业务数据转换成与底图一致的加密坐标,或者 把加密底图反算回 WGS-84。前者适合只在国内展示的场景,后者适合 全球数据统一管理。无论选哪条,转换都必须发生在数据进入图层之前, deck.gl 不会替你做这一步。

叠加顺序与透明度

layers 数组的顺序就是绘制顺序。底图放第一位, 矢量数据放后面。若底图颜色太亮,可以在数据图层用半透明填充, 也可以在 BitmapLayer 上覆盖一层纯色 PolygonLayer 做压暗,比调瓦片样式更可控。

倾斜视角下,瓦片边缘可能出现细缝。把 tileSize 与瓦片实际 分辨率对齐(常见 256 或 512),并给 BitmapLayer 关闭 textureParameters 中的线性过滤,可以减轻接缝。

与 MapLibre 集成

MapLibre 负责底图、注记和相机,deck.gl 通过 MapboxOverlay 作为自定义图层插入。关键点是让 deck.gl 使用地图的 viewState,而不是自己再维护一套相机。

var overlay = new deck.MapboxOverlay({
  interleaved: false,               // 需要与三维建筑混合时设为 true
  layers: [dataLayer]
});

map.addControl(overlay);

interleaved: true 让 deck.gl 与地图共用深度缓冲, 数据可以被建筑遮挡;代价是两侧渲染顺序强耦合,调试变复杂。 二维叠加场景保持 false 更稳。

与 Leaflet 联动的同步点

Leaflet 用 [纬度, 经度],deck.gl 用 [经度, 纬度]。 叠加时要监听 Leaflet 的 move 与 zoom 事件, 把中心点和层级换算成 deck.gl 的 viewState:

map.on('move', syncView);

function syncView() {
  var c = map.getCenter();
  deckgl.setProps({
    viewState: {
      longitude: c.lng,
      latitude: c.lat,
      zoom: map.getZoom() - 1,      // Leaflet 与 deck.gl 的缩放级别差 1
      pitch: 0,
      bearing: 0
    }
  });
}

缩放级别差 1 是因为 Leaflet 的 0 级瓦片是 256 像素覆盖全球, deck.gl 的 0 级按 512 像素计算。不同版本可能调整,首次集成时 用同一城市的两个已知点校准一次更可靠。

内网与离线瓦片

内网部署时把 data 指向自建瓦片服务即可,格式仍是 {z}/{x}/{y}.png。若瓦片带鉴权,TileLayer 本身不附带请求头,需要在 renderSubLayers 里用自定义 loadOptions.fetch 注入头部,或让网关按来源 IP 放行。

先验证底图,再叠加数据。底图空白时不要急着调数据图层: 先确认瓦片请求返回 200 且图像不是透明空图,再检查 boundingBox 的经纬度顺序。两步都通过后, 数据不对基本只剩坐标偏移或投影不匹配两种原因。

引用本文

经纬志(2026)。《与底图结合:瓦片、投影与偏移》。https://www.jingweizhi.com/deckgl/tutorial/basemap.html(访问日期 2026-11-03)。