中国地图在百度高德上为什么"歪"?WGS84 与 GCJ-02 坐标系这点事
先说个真事。前阵子我做一个全国行政区划边界的项目(GeoJSONcn,后面会提到),把一份标准的 GeoJSON 直接丢进高德地图,结果整张中国地图往东南方向飘了大概几百米到一公里。省界对不上、市界错位,肉眼看特别别扭。
查了一圈才反应过来:这根本不是数据错了,是坐标系的事儿。
两个坐标系,两种"真相"
平时能碰到地理坐标,基本就这几类:
- WGS84:GPS 卫星用的坐标系,绝大多数公开地理数据生来就是它。OpenStreetMap、天地图的原始数据、各种国际标准的 GeoJSON 大多走这个。
- GCJ-02(俗称"火星坐标"):国家为了安全加的一层加密偏移。高德、腾讯、百度(百度还叠了一层 BD-09)用的都是它。它是在 WGS84 基础上加了一个非线性随机偏移,而且是单向的,你很难逆向精确还原。
所以"为什么地图歪了"答案很直白:你的数据是 WGS84,底图却是 GCJ-02,两边没对齐,当然飘。
(实用建议放这儿:做中国地图可视化,要么把数据转成 GCJ-02 再上高德百度,要么用支持 WGS84 的底图,比如 Leaflet 加天地图 WGS84 图层、或者 Mapbox。别硬怼。)
怎么转?给一段能跑的代码
WGS84 转 GCJ-02 是公开的,核心就是一组椭圆偏移算法。下面这段 Node/JS 直接能用:
const PI = Math.PI;
const a = 6378245.0; // 长半轴
const ee = 0.00669342162296594323; // 偏心率平方
function outOfChina(lng, lat) {
return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
function transformLat(lng, lat) {
let ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat
+ 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320.0 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0;
return ret;
}
function transformLng(lng, lat) {
let ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng
+ 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng));
ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 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 - ee * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI);
dLng = (dLng * 180.0) / (a / sqrtMagic * Math.cos(radLat) * PI);
return [lng + dLng, lat + dLat];
}
这段代码我自己在数据清洗流水线里用了很久,稳。反向(GCJ-02 转 WGS84)没有精确公式,一般是迭代逼近,误差在厘米级,普通业务完全够用。
为什么我坚持用 WGS84 存原始数据
在 GeoJSONcn 这个项目里,所有原始边界数据我都按 WGS84 存。原因很简单:
1. 它是真相基线,转 GCJ-02 是单向有损的,留着 WGS84,以后想对接任何国际工具都方便; 2. 用天地图的 WGS84 矢量图层做底图,数据直接叠,零转换; 3. 用户要导进自己的 GIS 软件(QGIS 之类),WGS84 是默认友好格式。
至于百度高德,让他们去适配用户,而不是我先把数据弄脏。
最后说两句
地图歪,十有八九是 WGS84 和 GCJ-02 没对齐,不是你数据错。转换代码公开的,上面那段直接抄。原始数据存 WGS84,展示时再按需转换,这是最省心的做法,也是我那个项目能对接各种工具的根。
要是你正好在找现成的全国省市区 WGS84 边界数据(省/市/区县/乡镇四级),可以看看 GeoJSONcn,数据都是 WGS84 标准坐标系,导进 GIS 工具基本不用二次处理。下一篇我们聊聊 GeoJSON 这个格式本身到底长啥样。