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

视图与坐标系

先看结论

视图决定「从哪里看」,坐标系决定「数据放在哪」。 两者都写错时,画面往往不是报错,而是一片空白或者整体错位, 所以需要先把它们分开理解。

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

View 与 viewState 的分工

View 描述视口在画布上的位置和投影方式, viewState 描述相机参数。一个 Deck 实例可以挂多个 View, 每个 View 有自己的 viewState,用 id 区分。

View投影典型用途
MapViewWeb Mercator,支持倾斜旋转常规二维与伪三维地图
OrbitView透视,绕目标点环绕模型、点云、城市体块
FirstPersonView透视,相机位于指定位置室内漫游、街景
OrthographicView正交,无近大远小二维图表、非地理坐标布局

deck.gl 里默认使用 MapView,所以只写 initialViewState 也能工作。一旦传入 views, 默认视图就被替换,必须显式给出每个视图的 x、y、width、height。

viewState 的字段

字段含义常用取值
longitude / latitude视图中心数据范围的中心点
zoom缩放级别,每加 1 视野缩小一半3–18
pitch俯仰角,0 为垂直俯视0–60
bearing旋转角,正北为 0,顺时针为正-180–180
minZoom / maxZoom缩放上下限防止缩到瓦片失效层级
maxPitch俯仰上限65,超过会翻到地平线以下

受控视角的写法是同时给 viewState 与 onViewStateChange:回调里把新状态存下来,再 setProps 回去。这样相机动画和用户拖拽可以共存, 动画期间临时把 controller 设成 false 能避免冲突。

var view = { longitude: 116.39, latitude: 39.9, zoom: 9, pitch: 0, bearing: 0 };

var deckgl = new deck.Deck({
  canvas: 'cv',
  width: '100%',
  height: '100%',
  viewState: view,
  controller: true,
  onViewStateChange: function (e) {
    view = e.viewState;
    deckgl.setProps({ viewState: view });
  },
  layers: []
});

多视图

鹰眼图、主图加剖面图这类布局,靠 views 数组实现。 每个视图给一个 id,图层通过 viewId 指定 出现在哪个视图里;不写 viewId 的图层会出现在所有视图中。

views: [
  new deck.MapView({ id: 'main' }),
  new deck.MapView({
    id: 'overview',
    x: 12, y: 12,
    width: 260, height: 180,
    controller: true
  })
]

视图可以各自维护 viewState,也可以共享同一个对象。 共享时,主图缩小,鹰眼图跟着缩小,用户会以为鹰眼失去了概览作用; 更常见的做法是鹰眼图固定在大范围、只显示一个范围框。

坐标系统

图层默认工作在 LNGLAT 下,坐标即经纬度。 当数据不是经纬度,而是局部平面坐标(工程坐标、模型坐标), 就要显式声明坐标系,否则点会落到几内亚湾附近。

常量坐标含义适用
COORDINATE_SYSTEM.LNGLAT经纬度绝大多数地理数据
COORDINATE_SYSTEM.LNGLAT_OFFSETS相对 coordinateOrigin 的经纬度差局部高精度数据,避免大数相减丢精度
COORDINATE_SYSTEM.METER_OFFSETS相对原点的东向、北向米数工程坐标、建筑内部定位
COORDINATE_SYSTEM.CARTESIAN任意笛卡尔坐标与地理无关的图形,配合 OrthographicView
new deck.ScatterplotLayer({
  id: 'local',
  data: sensors,                          // {east: 120, north: -40}
  coordinateSystem: deck.COORDINATE_SYSTEM.METER_OFFSETS,
  coordinateOrigin: [116.39, 39.90],     // 局部坐标的原点
  getPosition: function (d) { return [d.east, d.north]; },
  getRadius: 6,
  radiusUnits: 'pixels'
});

METER_OFFSETS 的东向对应经度方向、北向对应纬度方向, 在原点附近近似成立;范围超过几十公里后,米与度的比例随纬度变化, 误差会明显增大。

屏幕坐标与地理坐标换算

自定义标注、图例联动、点击定位都需要在两种坐标之间转换。 WebMercatorViewport 提供了 project 与 unproject:

var viewport = new deck.WebMercatorViewport({
  longitude: 116.39,
  latitude: 39.9,
  zoom: 10,
  width: window.innerWidth,
  height: window.innerHeight
});

var px = viewport.project([116.40, 39.91]);   // -> [x, y]
var ll = viewport.unproject([px[0], px[1]]); // -> [lng, lat]

需要把一批点全部纳入视野时,用 fitBounds 反算 viewState, 比手算 zoom 可靠:

var bounds = [[73.5, 18.2], [135.1, 53.6]];   // [[西, 南], [东, 北]]
var fitted = viewport.fitBounds(bounds, { padding: 60 });
deckgl.setProps({ viewState: fitted });

像素与米

视图缩放会改变地面分辨率,因此长度类属性必须明确单位。 radiusUnits: 'pixels' 适合标记,缩放时大小恒定; 'meters' 适合表达真实范围,比如热力半径、道路宽度。 混用时最容易出现「低层级糊成一片、高层级看不见」的现象。

用 radiusMinPixels 与 radiusMaxPixels 夹住上下限,可以在两种单位之间取得折中: 地面尺寸随缩放变化,但不会小到不可见或大到遮挡全屏。

常见错误

视图和图层用了两套中心点

图层数据在成都,视图中心在北京,画面看起来「什么都没有」, 其实数据在屏幕外。初始化时用数据范围的中心,或直接用 fitBounds。

改变画布尺寸后视图错位

WebMercatorViewport 依赖画布宽高。容器尺寸变化后 必须同步 setProps({width, height}),否则 project 的结果会整体偏移,点击位置也跟着错。

认为 pitch 越大越立体

超过 65 度后,地平线进入画面,远处数据被裁掉,同时透视变形严重。 需要更强的立体感时,提高 zoom 并配合 getElevation,比继续加 pitch 更可控。

引用本文

经纬志(2026)。《视图与坐标系》。https://www.jingweizhi.com/deckgl/tutorial/views-coordinates.html(访问日期 2026-11-03)。