WebGIS 的第一道坎不是渲染管线,而是坐标系。这篇文章梳理了网络地图中所有主流坐标系的来龙去脉,比较 WGS84 和 CGCS2000 的异同,讲清楚"不同数据源加载到不同坐标系地图"这个经典问题怎么解,最后横向对比 gcoord 和 proj4 两个坐标转换库。
1. 网络地图常用坐标系一览
打开任何一个地图 SDK 的文档,"坐标系"三个字一定写在最前面。国内互联网地图至少涉及 四套坐标系,它们之间的关系可以用一句话概括:
原始卫星定位数据(WGS84)→ 国家测绘局加密(GCJ02)→ 各厂商二次加密(BD09 / 其他)
下面逐个梳理。
1.1 WGS84(World Geodetic System 1984)
WGS84 是 GPS 定位系统的底层坐标系,也是 CesiumJS、Google Earth、ArcGIS 等国际 GIS 平台的默认坐标系。
| 特征 | 说明 |
|---|---|
| 定义机构 | 美国国防部 / NGA |
| 椭球体 | WGS84 椭球(长半轴 6378137.0m,扁率 1/298.257223563) |
| 覆盖范围 | 全球 |
| 使用场景 | GPS 原始坐标、国际卫星影像、航海航空导航、CesiumJS/Leaflet/Mapbox GL JS(国际版) |
| 精度 | 民用 GPS 约 3-5 米,军码精度更高 |
记忆点:WGS84 = GPS 返回的"原始坐标"。你在野外打开手机 GPS 拿到的经纬度就是 WGS84。
1.2 GCJ02(国测局坐标系 / 火星坐标系)
GCJ02 是中国国家测绘局 2002 年发布的加密坐标系,强制性标准——所有在中国境内公开发布的地图产品必须使用 GCJ02。
| 特征 | 说明 |
|---|---|
| 定义机构 | 中国国家测绘局(现自然资源部) |
| 加密方式 | 在 WGS84 基础上加入非线性偏移(算法保密),偏移量在国内约 50-500 米,境外区域不偏移 |
| 覆盖范围 | 中国境内有效偏移,境外与 WGS84 基本一致 |
| 使用场景 | 高德地图、腾讯地图、Google 中国地图、图吧地图、搜狗地图 |
| 法律地位 | 2002 年《公开地图内容表示若干规定》强制要求 |
GCJ02 的偏移不是简单的平移或缩放——偏移量随经纬度而变化,且算法不公开。库文件通过逆向拟合或查表实现转换。
记忆点:GCJ02 = 国内地图"法定坐标系"。高德、腾讯的底图切片和检索结果都是 GCJ02,直接拿 WGS84 坐标传给它们会偏。
1.3 BD09(百度坐标系)
百度在 GCJ02 基础上做了二次加密,形成自己独立的坐标体系。
| 特征 | 说明 |
|---|---|
| 定义机构 | 百度 |
| 加密方式 | GCJ02 → 再加一层偏移(同样是保密的非线性变换) |
| 偏移量 | 在 GCJ02 基础上再偏移约 5-30 米 |
| 使用场景 | 百度地图全系列产品 |
| 特点 | 仅百度一家使用,形成了事实上的"封闭生态" |
记忆点:BD09 = 百度的"私有坐标系"。WGS84 → GCJ02 → BD09 是三层转换链,WGS84 → BD09 需要经过 GCJ02 中转。
1.4 CGCS2000(2000 国家大地坐标系)
CGCS2000(China Geodetic Coordinate System 2000)是中国自主建立的现代大地坐标系,2018 年 7 月 1 日起全面取代此前的西安 80 和北京 54 坐标系。
| 特征 | 说明 |
|---|---|
| 定义机构 | 中国自然资源部 |
| 椭球体 | 自定义椭球(长半轴 6378137.0m,扁率 1/298.257222101) |
| 参考历元 | 2000.0 |
| 立法地位 | 2018 年全面启用,取代 1954 北京坐标系和 1980 西安坐标系 |
| 使用场景 | 测绘、国土、规划、自然资源管理等专业测绘领域 |
| 与 WGS84 差异 | 厘米级(见下节详细对比) |
1.5 Web Mercator(EPSG:3857)
严格来说它不是坐标系,而是一种投影——把 WGS84(或 GCJ02)椭球面上的经纬度投影到平面。但实践中几乎所有 Web 地图的瓦片服务都以它为数学基础,所以必须纳入讨论。
| 特征 | 说明 |
|---|---|
| EPSG 编码 | 3857(曾用 900913) |
| 投影方式 | 球面墨卡托——把地球当正球体(半径 6378137m)做墨卡托投影 |
| 范围 | 经度 ±180°,纬度约 ±85.051129° |
| 瓦片服务 | Google、OSM、高德、百度等几乎所有互联网地图 |
记忆点:
EPSG:3857= Web 地图的通用瓦片网格。所以把高德瓦片贴到 CesiumJS 上时,需要解决的是坐标加密(GCJ02↔WGS84)问题,投影反而是统一 Web Mercator 不用操心。
2. WGS84 vs CGCS2000 —— 同名不同质
WGS84 和 CGCS2000 经常被混为一谈,因为它们的椭球参数极其相似:
| 参数 | WGS84 | CGCS2000 | 差异 |
|---|---|---|---|
| 长半轴 a | 6378137.0 m | 6378137.0 m | 完全一致 |
| 扁率 1/f | 298.257223563 | 298.257222101 | 仅在小数点后第 6 位有差异 |
| 地心引力常数 GM | 3.986004418×10¹⁴ | 3.986004418×10¹⁴ | 一致 |
| 自转角速度 ω | 7.2921150×10⁻⁵ | 7.2921150×10⁻⁵ | 一致 |
本质区别
参数几乎一样,但在大地测量学上它们是两个不同的参考框架:
-
参考历元不同:CGCS2000 的坐标定义在 2000.0 历元,WGS84 坐标定义在 1984.0 历元。板块以每年几厘米的速度漂移,历元差距 16 年意味着约 0.5~1 米的系统偏差。
-
实现方式不同:WGS84 通过全球分布的 GPS 监测站网维持和更新(目前已迭代到 WGS84(G2139));CGCS2000 通过中国的连续运行参考站(CORS)和 GNSS 控制网维持。
-
定位精度差异:在中国境内,CGCS2000 的站点密度和控制精度优于 WGS84,尤其在地籍测量、工程建设等高精度场景中优势明显。
在 Web 地图开发中这一讲可以忽略
对于 WebGIS 开发,二者差异在厘米量级,小于瓦片本身的屏幕精度。在代码中可以把 CGCS2000 坐标直接当 WGS84 使用,无需转换。
工程结论:Web 地图场景下 WGS84 ≈ CGCS2000,区别只在测绘级精度(亚米以下)才需要关注。
3. 核心问题:不同数据源加载到不同坐标系地图
这是 WebGIS 开发中最常见的翻车现场。
3.1 问题描述
假设你有三份数据:
| 数据 | 来源坐标系 | 数据内容 |
|---|---|---|
| GeoJSON 点数据 | WGS84(手采 GPS) | 某城市 POI 坐标 |
| 高德瓦片底图 | GCJ02 | 道路 + 建筑标注 |
| 百度地图 API 返回的检索结果 | BD09 | 周边商铺 |
把它们全部放到 WGS84 系的 CesiumJS 地球上:
- WGS84 的 GeoJSON → ✅ 准确落在对应位置
- 高德瓦片 → ❌ 偏移 50-500 米,路网和建筑对不上
- 百度检索结果 → ❌ 偏移更大(GCJ02 偏移 + BD09 偏移叠加)
反过来,如果换成高德地图 SDK(GCJ02 底盘):
- WGS84 数据 → ❌ 偏移,点标在马路对面
- 高德瓦片 → ✅ 准确
- 百度检索结果 → ❌ 偏移 5-30 米
这就是多源数据混合加载时的坐标系冲突——没有哪一个底图能同时容纳所有坐标系的数据而不发生偏移。
3.2 解决思路
核心思路只有一句:确保显示坐标系与数据源坐标系保持一致。 实现路径有三条:
方案 A:统一转换数据,保持底图不变
适用场景:以国际地图引擎(CesiumJS / Leaflet / Mapbox GL JS)为底盘,集成国内数据源。
工作流:
- 底图保持 WGS84(如 ArcGIS / Bing / 天地图 CGCS2000)。
- 所有非 WGS84 数据源在加载前统一转换为 WGS84。
- 如果要叠加高德/腾讯等 GCJ02 瓦片,在 ImageryProvider / TileLayer 中嵌入坐标纠偏逻辑(project 时 WGS84→GCJ02 请求瓦片,unproject 时 GCJ02→WGS84 反算位置)。
这是我们上一篇文章 《CesiumJS 加载高德卫星图,靠谱的 GCJ02 纠偏方案》 里采用的方案。
方案 B:统一转换底图,保持数据不变
适用场景:以国内地图 SDK(高德 / 腾讯 / 百度)为底盘。
- 底图天然是 GCJ02/BD09,不做修改。
- 所有 WGS84 数据在传入 SDK 前统一转为 GCJ02/BD09。
- 优点是数据侧转换简单(只转 WGS84→目标坐标系),缺点是换一家 SDK 就要反过来再转一次。
方案 C:在可视化层做实时转换
适用场景:同时接入多个不同坐标系的数据源、需要灵活切换。
这种方案的典型实现是维护一个统一的中间坐标系(通常选 WGS84 作为枢纽),所有数据在入库时标注其原生坐标系,渲染时按目标底图动态转换。
WGS84 ←→ GCJ02 ←→ BD09
↑ ↑ ↑
└─── WGS84(统一枢纽)───┘
3.3 实践中的经典翻车清单
- 高德检索结果直接标到 CesiumJS 地球上 —— 高德 SDK 返回的坐标是 GCJ02,CesiumJS 地球是 WGS84。需要
GCJ02 → WGS84转换。 - 百度地图 geocoding 结果喂给高德底图 —— BD09 数据在 GCJ02 底盘上也偏 5-30 米。需要
BD09 → GCJ02转换(或BD09 → WGS84 → GCJ02)。 - 野外采集的 GPS 轨迹(WGS84)直接拼到高德地图上 —— 反了,需要
WGS84 → GCJ02。 - 天地图的 CGCS2000 标注和国外 WGS84 服务叠加 —— 这个不需要转换,Web 精度下没差别。太多人在这里浪费了时间。
4. 坐标转换库对比:gcoord vs proj4
解决了"要不要转换"的问题,接下来是"用什么转换"的问题。目前 JavaScript 生态中两个主流选择是 gcoord 和 proj4js。
4.1 gcoord —— 为国内地图场景而生
gcoord 是一个专门为中国互联网地图坐标系设计的轻量转换库。
核心特性:
| 维度 | 说明 |
|---|---|
| 体积 | ~20KB(minified),无外部依赖 |
| 覆盖坐标系 | WGS84、GCJ02、BD09(LL + MC)、BD09Meter |
| API 设计 | 单一函数 gcoord.transform([lng, lat], from, to) |
| 批量转换 | 支持 gcoord.transform(arr2d, ...) 二维数组批量处理 |
| GeoJSON 转换 | gcoord.transformGeoJSON(geojson, from, to) 一行转换整个 GeoJSON |
| 额外工具 | gcoord.format([lng, lat]) 度分秒格式化 |
| 精度 | GCJ02↔WGS84 转换精度优于 0.5 米 |
适用场景:
- 国内互联网地图开发(高德、腾讯、百度、天地图基础层)
- WGS84 / GCJ02 / BD09 三种坐标系的任意互转
- 需要在浏览器端轻量(~20KB)、无额外构建配置的转换
代码示例:
// 单个坐标转换
const gcj = gcoord.transform([116.404, 39.915], gcoord.WGS84, gcoord.GCJ02);
// → [116.410, 39.917]
// 批量转换
const coords = [[116.404, 39.915], [121.473, 31.230]];
const gcjs = gcoord.transform(coords, gcoord.WGS84, gcoord.GCJ02);
// GeoJSON 全量转换(修改原对象)
gcoord.transformGeoJSON(geojson, gcoord.BD09, gcoord.WGS84);
4.2 proj4js —— 通用坐标参考系统转换引擎
proj4js 是 PROJ(C 语言地理空间坐标转换库)的 JavaScript 实现,为所有 EPSG 注册的坐标参考系统(CRS)提供通用转换能力。
核心特性:
| 维度 | 说明 |
|---|---|
| 体积 | ~170KB(minified),核心 ~100KB,需额外加载投影定义 |
| 覆盖坐标系 | 所有 EPSG 注册的 CRS(数万种),理论上无上限 |
| API 设计 | proj4(fromProj, toProj, [x, y]),投影用 PROJ 字符串或 EPSG 代码定义 |
| 自定义投影 | 支持 WKT 字符串、PROJ 字符串、ESRI WKT 等格式定义任意投影 |
| 绑定集成 | OpenLayers 内置绑定;Proj4Leaflet 为 Leaflet 提供 CRS 扩展 |
| 精度 | 取决于投影定义的链式传递精度,理论精度极高 |
适用场景:
- 涉及不同投影的坐标系转换(如 UTM ↔ Web Mercator ↔ 兰伯特等角)
- 国际项目,需要处理多国坐标参考系统
- 测绘专业场景,需要自定义椭球参数或投影
- 连接旧图纸坐标系统(如 1954 北京坐标系、1980 西安坐标系的 3 度 / 6 度带投影)
代码示例:
// 经典的长安街定位——WGS84 → Web Mercator
const wgs84To3857 = proj4("EPSG:4326", "EPSG:3857", [116.404, 39.915]);
// → [12958826.42, 4843953.52]
// CGCS2000 3度带(中央子午线 117°)→ WGS84(测绘场景)
proj4.defs("EPSG:4547",
"+proj=tmerc +lat_0=0 +lon_0=117 +k=1 +x_0=500000 +y_0=0 " +
"+ellps=GRS80 +units=m +no_defs"
);
proj4("EPSG:4547", "EPSG:4326", [503412.34, 4420756.12]);
4.3 横向对比
| 对比维度 | gcoord | proj4js |
|---|---|---|
| 体积 | ~20 KB | ~170 KB(~8.5 倍) |
| 学习成本 | 极低,2 个函数覆盖所有需求 | 需要理解 CRS / WKT / PROJ 字符串概念 |
| 国内坐标系 | 内置 WGS84 / GCJ02 / BD09 互转 | 不内置——GCJ02 和 BD09 是无公开参数的加密变换,不属于标准 EPSG 注册 |
| 投影变换 | 不支持(仅做经纬度偏移) | 原生支持,覆盖所有标准 EPSG 投影 |
| 可扩展性 | 不可扩展,专注三种坐标系 | 通过 PROJ/WKT 字符串自定义任意 CRS |
| 集成生态 | 独立使用 | OpenLayers / Leaflet 官方支持 |
4.4 选库建议
闭眼选 gcoord 的情况:
- 你只在中国互联网地图三兄弟(高德 / 腾讯 / 百度)之间折腾
- 需要 WGS84 ↔ GCJ02 ↔ BD09 转换
- 追求轻量和即插即用
- 场景示例:CesiumJS 上加载高德瓦片、百度 API 结果标到 GCJ02 地图、GPS 轨迹落到高德底图
选 proj4js 的情况:
- 需要做投影变换(经纬度 ↔ Web Mercator ↔ UTM ↔ 自定义分带投影)
- 对接测绘专业数据(带带号的北京 54、西安 80、CGCS2000 平面坐标)
- 国际项目,需要对接各地 SRID
- 场景示例:从测绘院拿到的 CGCS2000 3 度带投影坐标转 WGS84、从 Shapefile 读取多投影数据
两者一起用:
在实际工程中这两个库可以互补——proj4js 做投影变换,gcoord 做密码偏移。比如从测绘部门拿到 CGCS2000 分带平面坐标 → proj4js 转为 CGCS2000 经纬度(≈WGS84)→ gcoord 转为 GCJ02 再传给高德 API。
CGCS2000 平面坐标 ──proj4js──▶ WGS84 经纬度 ──gcoord──▶ GCJ02 ──▶ 高德底图
这条链路同时依赖两个库,各自负责自己擅长的部分,没有重叠。
5. 总结
坐标系问题说到底是三件事:知道数据在哪个坐标系 → 知道目标地图在哪个坐标系 → 选择合适的转换链路。
| 场景 | 转换方向 | 推荐工具 |
|---|---|---|
| GPS 轨迹放高德地图 | WGS84 → GCJ02 | gcoord |
| 高德检索结果放 CesiumJS | GCJ02 → WGS84 | gcoord |
| 百度地图数据放高德 | BD09 → GCJ02 | gcoord |
| 测绘平面坐标转经纬度 | 投影坐标 → WGS84 | proj4js |
| CesiumJS 加载高德瓦片 | WGS84 ↔ GCJ02(双向) | gcoord + 自定义 TilingScheme |
| CGCS2000 在 Web 地图中 | 无需转换,≈WGS84 | — |
坐标系就是 WebGIS 的"货币兑换"——所有数据在各自的坐标体系里都是"对的",但放在一起需要"汇率"。这篇文章梳理了四种主流坐标系、WGS84 与 CGCS2000 的细微差异、多源数据加载的核心解决方案,以及 gcoord 和 proj4js 两个工具的选择思路。希望能帮你在面对坐标系问题时少踩几个坑。