WGS84 转 GCJ02 原理与 JavaScript 实现:GPS 坐标如何转换为高德/腾讯地图坐标

在国内地图开发中,经常会遇到这样一个问题:

GPS 获取到的经纬度,直接放到高德地图或腾讯地图上,位置会出现明显偏移。

这通常不是 GPS 定位错误,而是因为两边使用的坐标体系不同

常见情况是:

  • GPS、部分 GIS 数据:WGS84

  • 高德地图:中国大陆范围内通常使用 GCJ-02

  • 腾讯地图:中国大陆范围内通常使用 GCJ-02

  • 百度地图:通常使用 BD-09

因此,如果手里拿到的是 GPS 的 WGS84 经纬度,而最终需要显示在高德、腾讯等国内地图服务中,就需要先完成:

复制代码
WGS84
  ↓
GCJ-02

本文介绍 WGS84 与 GCJ-02 的区别,并给出一份可以直接使用的 JavaScript 转换代码。


一、WGS84 是什么?

WGS84,全称:

复制代码
World Geodetic System 1984

是一套全球广泛使用的地理坐标参考系统。

我们日常通过 GPS、GNSS 设备获取到的经纬度,很多情况下就是基于 WGS84。

例如:

复制代码
经度:116.397428
纬度:39.909230

这种坐标可以用于:

  • GPS 定位

  • GIS 数据处理

  • GPS 轨迹

  • 国际地图数据

  • OpenStreetMap 等地理数据场景

  • 户外设备定位数据

但如果直接把 WGS84 坐标用于部分中国大陆互联网地图服务,就可能看到位置出现偏移。


二、GCJ-02 是什么?

GCJ-02 经常被开发者称为:

复制代码
火星坐标系

在中国大陆的互联网地图开发场景中非常常见。

例如高德地图、腾讯地图等服务,在中国大陆范围内通常涉及 GCJ-02 坐标。

因此常见的数据流程是:

复制代码
GPS设备
   ↓
WGS84
   ↓
坐标转换
   ↓
GCJ-02
   ↓
高德 / 腾讯地图

如果没有进行转换:

复制代码
WGS84坐标
   ↓
直接放入GCJ02地图
   ↓
出现位置偏移

所以开发地图项目时,第一件需要确认的事情往往不是:

经纬度是多少?

而是:

这个经纬度到底属于什么坐标系?


三、为什么两个坐标看起来很接近,却不能直接混用?

例如某个位置经过坐标转换后,可能出现类似:

复制代码
WGS84
116.391xxx,39.907xxx

GCJ02
116.397xxx,39.908xxx

两组数字看起来差别不算特别大。

但经纬度的小数变化映射到真实地理位置以后,可能已经是数百米级别的位置差异。

所以:

复制代码
WGS84 ≠ GCJ02

不能因为:

"经纬度看起来差不多"

就直接当成同一种坐标使用。

这也是很多地图"定位偏了"的真正原因。


四、JavaScript 实现 WGS84 → GCJ02

下面实现一个基础的 JavaScript 坐标转换模块。

复制代码
const PI = Math.PI;
const AXIS = 6378245.0;
const OFFSET = 0.00669342162296594323;

function outOfChina(lng, lat) {
  return (
    lng < 72.004 ||
    lng > 137.8347 ||
    lat < 0.8293 ||
    lat > 55.8271
  );
}

function transformLat(x, y) {
  let ret =
    -100.0 +
    2.0 * x +
    3.0 * y +
    0.2 * y * y +
    0.1 * x * y +
    0.2 * Math.sqrt(Math.abs(x));

  ret +=
    ((20.0 * Math.sin(6.0 * x * PI) +
      20.0 * Math.sin(2.0 * x * PI)) *
      2.0) / 3.0;

  ret +=
    ((20.0 * Math.sin(y * PI) +
      40.0 * Math.sin((y / 3.0) * PI)) *
      2.0) / 3.0;

  ret +=
    ((160.0 * Math.sin((y / 12.0) * PI) +
      320 * Math.sin((y * PI) / 30.0)) *
      2.0) / 3.0;

  return ret;
}

function transformLng(x, y) {
  let ret =
    300.0 +
    x +
    2.0 * y +
    0.1 * x * x +
    0.1 * x * y +
    0.1 * Math.sqrt(Math.abs(x));

  ret +=
    ((20.0 * Math.sin(6.0 * x * PI) +
      20.0 * Math.sin(2.0 * x * PI)) *
      2.0) / 3.0;

  ret +=
    ((20.0 * Math.sin(x * PI) +
      40.0 * Math.sin((x / 3.0) * PI)) *
      2.0) / 3.0;

  ret +=
    ((150.0 * Math.sin((x / 12.0) * PI) +
      300.0 * Math.sin((x / 30.0) * PI)) *
      2.0) / 3.0;

  return ret;
}

function wgs84ToGcj02(lng, lat) {
  if (outOfChina(lng, lat)) {
    return [lng, lat];
  }

  let dLat = transformLat(lng - 105.0, lat - 35.0);
  let dLng = transformLng(lng - 105.0, lat - 35.0);

  const radLat = (lat / 180.0) * PI;

  let magic = Math.sin(radLat);
  magic = 1 - OFFSET * magic * magic;

  const sqrtMagic = Math.sqrt(magic);

  dLat =
    (dLat * 180.0) /
    (((AXIS * (1 - OFFSET)) / (magic * sqrtMagic)) * PI);

  dLng =
    (dLng * 180.0) /
    ((AXIS / sqrtMagic) * Math.cos(radLat) * PI);

  const gcjLat = lat + dLat;
  const gcjLng = lng + dLng;

  return [gcjLng, gcjLat];
}

调用方式:

复制代码
const result = wgs84ToGcj02(
  116.397428,
  39.909230
);

console.log(result);

返回的是:

复制代码
[GCJ02 经度, GCJ02 纬度]

五、实际项目中建议封装成对象返回

数组虽然简单,但业务项目中容易搞混经纬度顺序。

复制代码
function convertWgs84ToGcj02(lng, lat) {
  const [gcjLng, gcjLat] = wgs84ToGcj02(lng, lat);

  return {
    longitude: gcjLng,
    latitude: gcjLat
  };
}

使用:

复制代码
const location = convertWgs84ToGcj02(
  116.397428,
  39.909230
);

console.log(location.longitude);
console.log(location.latitude);

这样代码会更清晰。


六、一定要统一"经度、纬度"的顺序

例如:

复制代码
116.397428,39.909230

这里:

复制代码
116.397428 = 经度 longitude
39.909230  = 纬度 latitude

建议整个项目统一约定:

复制代码
longitude, latitude

即:

复制代码
经度,纬度

代码同样统一:

复制代码
convert(lng, lat);

不要有的模块使用 (lat, lng),另一些模块使用 (lng, lat)


七、为什么需要 outOfChina 判断?

代码中的 outOfChina 用于判断坐标是否明显处于 GCJ-02 常见适用区域之外。

例如纽约、伦敦、东京、巴黎等坐标,一般没有必要按照中国大陆 GCJ-02 的转换逻辑处理。

需要注意:

这种经纬度边界判断属于工程上的近似范围判断,并不等同于精确国界多边形判断。

如果业务对边界地区要求非常严格,应使用更精确的地理范围数据。


八、前端输入一定要进行参数校验

经度合法范围:

复制代码
-180 ~ 180

纬度合法范围:

复制代码
-90 ~ 90

可以封装:

复制代码
function isValidCoordinate(lng, lat) {
  return (
    Number.isFinite(lng) &&
    Number.isFinite(lat) &&
    lng >= -180 &&
    lng <= 180 &&
    lat >= -90 &&
    lat <= 90
  );
}

尤其要注意:

复制代码
Number("")

结果是:

复制代码
0

所以表单中最好先判断输入是否为空。


九、批量转换怎么实现?

如果需要处理:

复制代码
116.397428,39.909230
121.473701,31.230416
113.264385,23.129112

可以按行处理:

复制代码
function batchConvert(text) {
  return text
    .split(/\r?\n/)
    .filter(Boolean)
    .map((line, index) => {
      const [lngText, latText] = line.split(",");

      const lng = Number(lngText);
      const lat = Number(latText);

      if (!isValidCoordinate(lng, lat)) {
        return {
          line: index + 1,
          success: false,
          error: "经纬度格式错误"
        };
      }

      const [gcjLng, gcjLat] =
        wgs84ToGcj02(lng, lat);

      return {
        line: index + 1,
        success: true,
        sourceLng: lng,
        sourceLat: lat,
        gcjLng,
        gcjLat
      };
    });
}

这样即使某一行错误,也不会导致整个批量任务失败。


十、GCJ02 转换常见错误

1. 把 WGS84 当成 GCJ02

GPS 获取的数据应先确认坐标体系,再决定是否转换。

2. 经纬度顺序写反

错误:

复制代码
wgs84ToGcj02(lat, lng);

正确:

复制代码
wgs84ToGcj02(lng, lat);

3. 重复转换

建议业务数据明确记录坐标体系:

复制代码
{
  longitude: 116.39,
  latitude: 39.90,
  coordinateSystem: "GCJ02"
}

这样可以避免后续重复转换。


十一、建议项目内部建立统一坐标类型

复制代码
const CoordinateSystem = {
  WGS84: "WGS84",
  GCJ02: "GCJ02",
  BD09: "BD09"
};

坐标对象:

复制代码
const point = {
  longitude: 116.397428,
  latitude: 39.909230,
  coordinateSystem: CoordinateSystem.WGS84
};

当项目逐渐涉及:

复制代码
WGS84
GCJ02
BD09
Web Mercator

这种统一设计会比在业务代码中到处直接调用转换函数更容易维护。


十二、坐标转换结果能做到完全无误差吗?

不建议把任何坐标转换工具理解成"绝对准确"。

实际效果还可能受到:

  • 原始 GPS 数据精度

  • 手机定位精度

  • GNSS 设备质量

  • 地图数据源

  • 坐标转换算法

  • 原始数据本身是否已经转换

  • 地图平台实际接口要求

等因素影响。

开发过程中最重要的是先确认:

复制代码
数据来源是什么坐标系?
↓
目标地图使用什么坐标系?
↓
是否已经被转换过?
↓
最终统一坐标体系

十三、推荐的数据处理流程

如果是 GPS 数据展示到国内地图:

复制代码
GPS / GNSS
    ↓
 WGS84
    ↓
判断目标地图
    ↓
GCJ02 转换
    ↓
高德 / 腾讯地图展示

如果后端保存的是原始 GPS 数据,更推荐:

复制代码
数据库保存原始 WGS84
        ↓
业务层根据目标平台转换
        ↓
前端地图展示

这样以后做 GIS 分析、国际地图或其他坐标体系转换时,仍然保留原始数据。


十四、在线测试 WGS84 → GCJ02

如果只是临时转换一个坐标,或者想快速验证自己的代码结果,可以直接使用在线 Demo:

在线 Demo: WGS84转GCJ02 - GPS坐标转高德火星坐标在线工具

支持:

  • WGS84 → GCJ02

  • 单个坐标转换

  • 经纬度快速粘贴

  • 批量坐标处理

  • 转换结果复制

  • 其他地图坐标工具


总结

WGS84 与 GCJ02 的核心关系,可以简单理解为:

复制代码
WGS84
GPS / GNSS / 国际地理数据常见
        ↓
     坐标转换
        ↓
GCJ02
中国大陆部分互联网地图服务常见

地图开发中最重要的并不是记住某一段转换代码,而是建立一个习惯:

任何经纬度数据进入系统之前,先确认坐标系。

只要把下面四件事情理清:

复制代码
坐标来源
目标地图
坐标体系
转换方向

大部分国内地图定位偏移问题都会容易处理很多。

相关推荐
天天进步20156 小时前
Python全栈项目--协同办公平台
开发语言·python
JAVA面经实录9176 小时前
集合框架 (六)
java·开发语言
xzlAwin6 小时前
Go语言多版本管理器g工具命令
开发语言·golang
少控科技6 小时前
农场设备管理代码(2)
开发语言·c#
周GZ7 小时前
简单讲解Java中静态方法与 实例方法
java·开发语言
大白同学4218 小时前
初识Qt——创建第一个Qt项目
开发语言·qt
祈禾8 小时前
计算机组成原理之编译、汇编、链接、解释
开发语言·汇编·笔记·学习
吴声子夜歌8 小时前
正则指引——Ruby
开发语言·正则表达式·ruby
nnerddboy9 小时前
Rust教程06:ESP32-rust环境搭建
开发语言·后端·rust
nnerddboy9 小时前
Rust教程03:函数,控制流与所有权
开发语言·后端·rust