视图与坐标系
视图决定「从哪里看」,坐标系决定「数据放在哪」。 两者都写错时,画面往往不是报错,而是一片空白或者整体错位, 所以需要先把它们分开理解。
View 与 viewState 的分工
View 描述视口在画布上的位置和投影方式,
viewState 描述相机参数。一个 Deck 实例可以挂多个 View,
每个 View 有自己的 viewState,用 id 区分。
| View | 投影 | 典型用途 |
|---|---|---|
MapView | Web 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)。