Kc's blog Kc's blog
首页
分类
标签
Timeline
收藏夹
关于
GitHub (opens new window)

kcqingfeng

前端小学生
首页
分类
标签
Timeline
收藏夹
关于
GitHub (opens new window)
  • 学习

  • AI

  • 面试

  • 心情杂货

  • 产品

  • 服务器实例

  • 实用技巧

  • 搞钱

    • 中安日报
    • 高德+deck坐标系与投影集成技术
      • 高德地图 + Deck.gl 混合渲染实践:坐标系、Web Mercator 投影与 WebGL 架构解析
      • 一、整体架构
        • 二、GCJ-02 与 WGS84
        • 三、为什么 Deck.gl 可以直接使用 GCJ02
        • 四、真正关键的原因:投影规则一致
        • 五、为什么高德 + Deck.gl 可以精准重合
        • 六、Web Mercator 投影原理
        • 七、ViewState 本质是什么
        • 八、为什么必须同步 center
        • 九、为什么 Zoom 会差 1
        • 十、为什么需要 zoom - 1
        • 十一、为什么拟合视口时需要 +1
        • 十二、WebGL Context 共享架构
        • 十三、GPU Picking 原理
        • 十四、空间围栏(Geofencing)
        • 十五、总结
        • 十六、最终效果
  • 更多
  • 搞钱
kc_shen
2026-05-21
目录

高德+deck坐标系与投影集成技术

# 高德地图 + Deck.gl 混合渲染实践:坐标系、Web Mercator 投影与 WebGL 架构解析

在 Web GIS 可视化场景中,尤其是:

  • OD 流向分析
  • 物流轨迹
  • 空间网络关系
  • 热点迁徙
  • 大规模点线渲染

通常都会采用:

  • 高德地图(AMap 2.0)
  • Deck.gl
  • FlowmapLayer

进行混合渲染。

但真正落地时,往往会遇到几个核心问题:

  • GCJ-02 与 WGS84 坐标系冲突
  • Deck.gl 与高德 Zoom 不一致
  • 点线 Overlay 拖拽漂移
  • 多 Canvas 渲染撕裂
  • 海量 WebGL Picking
  • 空间围栏与区域归属判定

本文基于实际项目,详细介绍:

  1. 高德 + Deck.gl 坐标系统对齐
  2. Web Mercator 投影原理
  3. 为什么 GCJ02 可以直接给 Deck.gl
  4. Zoom -1 / +1 的本质
  5. WebGL Context 共享架构
  6. GPU Picking 原理
  7. pointInPolygon 空间围栏实现

# 一、整体架构

整个系统采用:

高德地图(AMap 2.0 WebGL)
        ↓
AMap.GLCustomLayer
        ↓
共享 WebGL Context
        ↓
Deck.gl
        ↓
FlowmapLayer
1
2
3
4
5
6
7
8
9

核心思想:

# 不创建第二层 Canvas

而是:

# 直接复用高德地图内部的 WebGL Context

这样能够保证:

  • 高性能渲染
  • 无拖拽撕裂
  • GPU 资源共享
  • 同步 RenderLoop
  • 移动端稳定性

# 二、GCJ-02 与 WGS84

国内 GIS 开发中最容易踩坑的问题之一:

# GCJ-02 与 WGS84


# 2.1 两者到底区别是什么?

很多人误以为:

GCJ02 和 WGS84 是两套不同的投影系统
1

实际上:

# 不是

它们:

# 使用的是同一套地理投影规则

区别仅仅是:

GCJ02 = 对 WGS84 做了加密偏移
1

例如:

坐标系 上海人民广场
WGS84 121.4737, 31.2304
GCJ02 121.4801, 31.2283

可以看到:

# 仍然是正常经纬度

只是:

数值整体发生了偏移
1

# 三、为什么 Deck.gl 可以直接使用 GCJ02

很多人会问:

Deck.gl 不是默认 WGS84 吗?
为什么 GCJ02 也能正常显示?
1
2

答案是:

# Deck.gl 并不会识别坐标系来源

它只会认为:

“这是 longitude / latitude”
1

然后直接进行:

# Web Mercator 投影计算


# 3.1 Deck.gl 真正做的事情

Deck.gl 内部核心流程:

经纬度
→ Web Mercator
→ 世界坐标
→ GPU 矩阵变换
→ 屏幕像素
1
2
3
4
5

它并不会检查:

  • 这是 WGS84?
  • 还是 GCJ02?
  • 还是 BD09?

因为:

# Web Mercator 本身不关心坐标来源

它只做数学投影。


# 四、真正关键的原因:投影规则一致

整个系统能成立的核心原因:

# GCJ02 与 WGS84 使用同一套 Web Mercator 投影规则

Web Mercator 核心公式:

x = R \cdot \lambda

y = R \cdot \ln\left(\tan\left(\frac{\pi}{4}+\frac{\phi}{2}\right)\right)

其中:

参数 含义
( \lambda ) 经度
( \phi ) 纬度
( R ) 地球半径

注意:

# 公式完全不会关心你是不是 GCJ02

它只认:

这是一个经纬度数字
1

# 五、为什么高德 + Deck.gl 可以精准重合

因为:

数据 坐标系
高德底图 GCJ02
点位数据 GCJ02
Flow 数据 GCJ02
center GCJ02

于是:

所有东西一起偏移
1

最终:

高德瓦片像素
=
Deck.gl 投影像素
1
2
3

所以:

# 即使整个世界偏移了

屏幕结果仍然完全重合。


# 六、Web Mercator 投影原理

无论:

  • 高德地图
  • Deck.gl
  • Mapbox GL

底层都基于:

# Web Mercator(EPSG:3857)


# 6.1 渲染流程

地图渲染本质:

LngLat
→ 世界坐标
→ View Matrix
→ Projection Matrix
→ ClipSpace
→ 屏幕像素
1
2
3
4
5
6

因此:

# 地图本质是矩阵变换

而不是简单绘制经纬度。


# 七、ViewState 本质是什么

Deck.gl 的:

viewState
1

本质上是:

# 虚拟相机参数

类似 Three.js:

camera.position
camera.rotation
camera.zoom
1
2
3

核心参数:

{
  longitude,
  latitude,
  zoom,
  pitch,
  bearing
}
1
2
3
4
5
6
7

# 八、为什么必须同步 center

这是很多人第一次接 Deck.gl 时最疑惑的问题。

为什么:

this.hkyMap.getCenter()
1

如此重要?

因为:

# center 是整个投影系统的原点

地图渲染并不是:

经纬度 → 屏幕像素
1

而是:

经纬度
→ 相对于 center 偏移
→ zoom 缩放
→ pitch/bearing 旋转
→ GPU 投影
1
2
3
4
5

如果不同步 center:

  • 高德底图已经移动
  • Deck.gl 仍停留旧视口

结果:

  • 点线漂移
  • Overlay 错位
  • Hover 不准确
  • 拖拽撕裂

# 8.1 ViewState 同步

private getDeckViewState() {
  const center = this.hkyMap.getCenter();

  return {
    longitude: center.lng,
    latitude: center.lat,
    zoom: this.hkyMap.getZoom() - 1,
    pitch: this.hkyMap.getPitch(),
    bearing: this.hkyMap.getRotation()
  };
}
1
2
3
4
5
6
7
8
9
10
11

本质上是在告诉 Deck.gl:

“当前地图相机的位置”
1

# 九、为什么 Zoom 会差 1

这是高德 + Deck.gl 集成中最大的坑之一。


# 9.1 本质原因:TileSize 不一致

# Deck.gl / Mapbox

默认:

tileSize = 512
1

世界尺度:

worldSize = tileSize × 2^zoom
1

即:

512 × 2^zoom
1

# 高德地图

内部仍采用:

tileSize = 256
1

即:

256 × 2^zoom
1

因此:

512 × 2^z
=
256 × 2^(z+1)
1
2
3

所以:

# 同一屏幕尺度下:

AMap Zoom ≈ Deck Zoom + 1
1

# 十、为什么需要 zoom - 1

同步高德视口时:

zoom: this.hkyMap.getZoom() - 1
1

否则:

Deck.gl 的比例尺会比高德放大一倍。

结果:

  • 点位偏移
  • FlowLine 错位
  • 拖拽撕裂
  • Hover 不准

# 十一、为什么拟合视口时需要 +1

使用:

getViewStateForLocations()
1

得到的是:

# 标准 Web Mercator Zoom

同步给高德时必须:

viewState.zoom + 1
1

例如:

const amapZoom = viewState.zoom + 1;

this.hkyMap.setZoomAndCenter(
  amapZoom,
  new AMap.LngLat(
    viewState.longitude,
    viewState.latitude
  )
);
1
2
3
4
5
6
7
8
9

# 十二、WebGL Context 共享架构

项目没有采用:

高德 Canvas
+
Deck.gl Overlay Canvas
1
2
3

因为会导致:

  • repaint 不同步
  • GPU 状态切换
  • 多 Canvas 撕裂
  • pointer offset
  • 移动端掉帧

因此:

# 使用 GLCustomLayer


# 12.1 WebGL Context 注入

this.deckCustomLayer = new AMap.GLCustomLayer({
  zIndex: 999,

  init: (gl: WebGLRenderingContext) => {
    this.deckOverlay = new Deck({
      gl,

      layers: this.deckLayers,

      viewState: this.getDeckViewState()
    });
  },

  render: () => {
    this.deckOverlay.setProps({
      viewState: this.getDeckViewState(),
      layers: this.deckLayers
    });
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这里:

gl
1

就是高德内部创建的 WebGL Context。

Deck.gl 直接复用:

同一 GPU
同一 Canvas
同一 RenderLoop
1
2
3

这是整个系统高性能的核心。


# 十三、GPU Picking 原理

由于 Deck.gl 没有自己的 DOM 容器:

所有:

  • click
  • hover
  • mousemove

都由高德底图监听。


# 13.1 坐标转换

this.deckClickHandler = (e: any) => {
  const rect =
    this.mapHostRef.nativeElement.getBoundingClientRect();

  const x =
    e.originEvent.clientX - rect.left;

  const y =
    e.originEvent.clientY - rect.top;

  const info =
    this.deckOverlay.pickObject({ x, y });
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# 13.2 Picking 原理

Deck.gl 使用:

# GPU Framebuffer Picking

流程:

对象
→ 写入唯一颜色ID
→ 离屏渲染
→ 读取像素颜色
→ 反查对象
1
2
3
4
5

因此:

即使:

  • 数千点位
  • 数万流线

依然能够保持高性能 Hover。


# 十四、空间围栏(Geofencing)

为了判断:

某个点是否属于区域
1

项目采用:

# 射线投射法(Ray Casting)


# 14.1 pointInPolygon

private pointInPolygon(
  lng: number,
  lat: number,
  path: [number, number][]
) {
  let inside = false;

  for (
    let i = 0, j = path.length - 1;
    i < path.length;
    j = i++
  ) {
    const xi = path[i][0];
    const yi = path[i][1];

    const xj = path[j][0];
    const yj = path[j][1];

    const intersect =
      yi > lat !== yj > lat &&
      lng <
        ((xj - xi) * (lat - yi)) /
          (yj - yi + 1e-12) +
        xi;

    if (intersect) {
      inside = !inside;
    }
  }

  return inside;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 14.2 原理

算法思想:

从目标点向右发射一条射线
1

如果:

  • 交点为奇数 → 在内部
  • 交点为偶数 → 在外部

# 十五、总结

整个系统的核心,本质上是:

# 两套地图引擎投影矩阵的一致性

只要保证:

  • 同一坐标系
  • 同一 center
  • 同一 zoom scale
  • 同一 pitch/bearing
  • 同一 WebGL Context

那么:

高德底图
=
Deck.gl Overlay
1
2
3

即可实现真正高性能、无撕裂的 GIS 混合渲染架构。


# 十六、最终效果

系统最终实现:

  • 海量 OD 流向渲染
  • GPU 动态流线
  • 高性能 Hover Picking
  • 区域联动
  • 空间过滤
  • 高德底图无缝融合
  • WebGL GPU 加速

并能够在复杂业务场景下保持稳定流畅的交互体验。

编辑 (opens new window)
上次更新: 2026/05/21, 8:05:00
中安日报

← 中安日报

最近更新
01
内网高德地图代理问题
05-19
02
OD流线图的集成
05-19
03
Mac 与 Windows 协同开发中的 Git 换行符问题
05-19
更多文章>
Theme by Vdoing | Copyright © 2019-2026 kc shen | MIT License 豫ICP备2024074563号-3
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式