经纬志
首页 / 实战手册 / 坐标与投影

同一批点位被转换两次,偏移从 555 米变成 1110 米

先看结论

GPS 点位在高德底图上偏出去半公里,接口里明明已经转过坐标了,前端又转了一遍。

最后验证 2026-11-03 · 多技术栈 · 更新 2026-10-29 · 引用本文

现象

北京国贸的 GPS 原始点 116.397428, 39.909230,正确转入 GCJ-02 后应为 116.403672, 39.910634,与原始点相距 555.6 米。 实际在高德底图上量出来是 116.409922, 39.912036,与原始点相距 1110 米左右, 方向仍然是东北,等于把 555.6 米的偏移量叠加了两次。

点位数越多越明显:单个点还能辩称是采集误差,整片 POI 统一错开一千米就只能说明处理链路有问题。

原因

GCJ-02 的加密是「给 WGS84 加一个与经纬度相关的增量」。这个操作不幂等: 把 f(x) 再套一层变成 f(f(x)),增量继续累加,而不是还原。 所以同一份数据被转两次,偏移量不会相互抵消,只会叠加。

典型链路:GIS 服务在入库时把 WGS84 转成 GCJ-02 存进数据库,但没有在数据里留任何坐标系标记; 前端拿到数据后,凭「接口给的是 GPS 坐标」的默认印象又调了一次 gcoord.transform(data, 'WGS84', 'GCJ02')。

判断依据看数值方向,不看绝对值。二次转换的结果仍然在原始点的东北方向, 距离恰好约等于一次偏移的两倍;如果是坐标系搞反(该逆转换却正转换),偏移方向会反过来。

排查过程

  1. 取一个已知点,记录接口返回的原始经纬度和地图上量到的经纬度,两者相减得到实际偏移向量。
  2. 用 WGS84→GCJ-02 正算算出「只转一次」应得的结果,与接口返回值比对。若接口返回已经等于该结果,说明后台已转过。
  3. 在浏览器网络面板复制接口响应,直接丢进 gcoord.transform 手动跑一次,看输出是否又往东北挪了一截。
  4. 检查前端初始化代码里是否还有一处全局的坐标转换中间件或 axios 响应拦截器。
// 用 turf 量一下实际偏移,确认是否翻倍
const a = turf.point([116.397428, 39.909230]);   // WGS84 原始点
const b = turf.point([116.403672, 39.910634]);   // 应得 GCJ-02
const c = turf.point([116.409922, 39.912036]);   // 实际渲染点

turf.distance(a, b, { units: 'meters' });  // 555.6
turf.distance(a, c, { units: 'meters' });  // 1110.2
turf.distance(b, c, { units: 'meters' });  // 554.6

解决方案

转换只能在链路的某一个环节做一次。推荐在数据产出端完成转换,并把坐标系写进数据本身, 让下游不需要猜。

// 1. 服务端输出时打标记(非标准 crs 成员,自定义字段更稳妥)
{
  "type": "FeatureCollection",
  "coordSys": "GCJ02",
  "features": [
    { "type": "Feature",
      "properties": { "name": "国贸" },
      "geometry": { "type": "Point", "coordinates": [116.403672, 39.910634] } }
  ]
}

// 2. 前端只按标记决定是否转换,不做无条件转换
const SRC = 'WGS84', DST = 'GCJ02';
function normalize(fc, target) {
  const from = fc.coordSys || 'WGS84';
  if (from === target) return fc;                    // 已经一致,直接返回
  return gcoord.transform(fc, gcoord[from], gcoord[target]);
}
const data = normalize(resp.data, 'GCJ02');

如果历史数据已经被污染,按「二次转换后的值反推一次」不可行, 必须回到原始 WGS84 数据重新导出,或向数据提供方索取原始坐标。

验证

取国贸点重跑链路,输出应为 116.403672, 39.910634, 与 WGS84 原始点距离 555.6 米(允许 ±1 米,因各家实现的浮点细节略有差异)。 再抽 10 个分布在乌鲁木齐、广州、哈尔滨的点,逐个核对偏移量落在 265~622 米区间内。

console.assert(Math.abs(turf.distance(a, out) - 555.6) < 1, '偏移量不符');

适用版本

库 / 服务版本说明
gcoord1.0.0 及以上gcoord.transform 不做幂等保护
turf.js6.x / 7.xturf.distance 默认按球面计算,单位可选 meters
高德 JS API2.0入参必须是 GCJ-02,二次转换同样叠加偏移
引用本文

经纬志(2026)。《同一批点位被转换两次,偏移从 555 米变成 1110 米》。https://www.jingweizhi.com/cookbook/gcj02-double-convert.html(访问日期 2026-11-03)。