坐标系与投影:地理坐标系(WGS84、GCJ02)、投影坐标系(墨卡托、UTM)、坐标系转换原理与代码实现
做轨迹回放,第一个绕不开的坎儿就是坐标系。
我记得刚入行那会儿,拿到一批GPS轨迹数据,直接往地图上怼,结果点位全飘到海里去了。当时我还纳闷,明明设备没问题啊。后来才发现,数据是WGS84的,地图用的是GCJ02——压根儿不在一个频道上。
所以这一章,咱们就把坐标系这事儿彻底捋清楚。
1. 地理坐标系:地球不是平的
说白了,地理坐标系就是用经纬度来描述地球上的位置。地球是个椭球体,不是正圆,所以不同国家、不同时期定义的椭球参数也不一样。
WGS84(世界大地测量系统1984)
这是GPS全球定位系统使用的坐标系。全球通用,精度高。我平时做海外项目,基本都拿它当基准。
- 原点:地球质心
- 椭球:WGS84椭球(长半轴6378137m,扁率1/298.257223563)
- 应用:GPS、Google Earth、大部分国际标准
GCJ02(国测局坐标)
这是中国特有的坐标系。嗯,说白了就是WGS84加了个非线性偏移。为什么?因为测绘法规定,国内公开地图必须使用GCJ02。
我在项目中遇到过最坑的事:用手机GPS采集的WGS84数据,直接叠加到高德地图上,偏差了500多米。后来才知道,高德、腾讯、百度(百度有自己的BD09)用的都是GCJ02。
我曾经在做一个国内物流轨迹项目时,直接把WGS84坐标传给高德API,结果轨迹全画到隔壁小区去了。后来加了一层坐标转换,才恢复正常。记住:国内地图服务商,必须用GCJ02。
2. 投影坐标系:把球面摊平
地球是三维的,屏幕是二维的。投影就是把球面上的经纬度,映射到平面上的x、y坐标。
你想想看,剥橘子皮的时候,硬压平肯定会裂开。投影也一样,会有变形。关键看你想要什么——保角度?保面积?还是保距离?
墨卡托投影(Mercator)
这是最经典的投影方式。特点是:等角,也就是角度不变。航海图用它,因为航线画出来是直线。
但代价是什么?面积变形严重。越靠近两极,变形越大。格陵兰岛看起来跟非洲差不多大,实际只有非洲的1/14。
Web墨卡托(EPSG:3857)是互联网地图的标配。Google Maps、OpenStreetMap都用它。我平时做轨迹回放,前端展示基本都转成这个坐标系。
UTM投影(通用横轴墨卡托)
UTM把地球分成60个带,每带6度经度。每个带单独投影,变形小很多。
我建议做高精度轨迹分析时,用UTM。比如计算两点间的实际距离、面积,UTM比Web墨卡托准得多。
| 投影类型 | 特点 | 适用场景 |
|---|---|---|
| Web墨卡托 | 等角、全球统一、变形大 | 地图展示、轨迹可视化 |
| UTM | 等角、分带、局部变形小 | 距离计算、面积测量、高精度分析 |
3. 坐标系转换原理
转换这事儿,其实就两种:
- 地理坐标系之间的转换(比如WGS84 → GCJ02)
- 地理坐标系到投影坐标系的转换(比如WGS84 → UTM)
第一种,说白了就是加一个偏移量。但这个偏移量不是线性的,跟经纬度有关。GCJ02的算法是国测局定的,网上有开源实现,但精度只能到米级。
第二种,本质是数学投影公式。墨卡托投影公式很简单,UTM稍微复杂一点,但都有标准算法。
转换不是万能的。WGS84转GCJ02,误差在10米以内算正常。如果你需要厘米级精度,建议直接用RTK设备采集GCJ02坐标,别转。
4. 代码实现:手写一个转换工具
下面是我常用的一个坐标转换工具类。包含WGS84转GCJ02、GCJ02转WGS84、WGS84转Web墨卡托。
// 坐标转换工具类
class CoordTransform {
// 常量:WGS84椭球参数
static const double a = 6378137.0;
static const double ee = 0.00669342162296594323;
// WGS84 → GCJ02
static List<double> wgs84ToGcj02(double lng, double lat) {
// 如果是国外,直接返回
if (isOutOfChina(lng, lat)) return [lng, lat];
double dlat = transformLat(lng - 105.0, lat - 35.0);
double dlng = transformLng(lng - 105.0, lat - 35.0);
double radlat = lat / 180.0 * pi;
double magic = sin(radlat);
magic = 1 - ee * magic * magic;
double sqrtmagic = sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi);
dlng = (dlng * 180.0) / (a / sqrtmagic * cos(radlat) * pi);
double mglat = lat + dlat;
double mglng = lng + dlng;
return [mglng, mglat];
}
// GCJ02 → WGS84(迭代法,精度更高)
static List<double> gcj02ToWgs84(double lng, double lat) {
if (isOutOfChina(lng, lat)) return [lng, lat];
double wgsLng = lng, wgsLat = lat;
for (int i = 0; i < 5; i++) {
List<double> gcj = wgs84ToGcj02(wgsLng, wgsLat);
wgsLng += lng - gcj[0];
wgsLat += lat - gcj[1];
}
return [wgsLng, wgsLat];
}
// WGS84 → Web墨卡托
static List<double> wgs84ToMercator(double lng, double lat) {
double x = lng * 20037508.34 / 180;
double y = log(tan((90 + lat) * pi / 360)) / (pi / 180);
y = y * 20037508.34 / 180;
return [x, y];
}
// 判断是否在中国境外
static bool isOutOfChina(double lng, double lat) {
return lng < 72.004 || lng > 137.8347 ||
lat < 0.8293 || lat > 55.8271;
}
// 辅助函数(省略具体实现,网上可查)
static double transformLat(double x, double y) { /* ... */ }
static double transformLng(double x, double y) { /* ... */ }
}
迭代法转GCJ02回WGS84,迭代5次就够了。再多精度提升有限,反而增加计算量。我在处理百万级轨迹点时,就用5次迭代,精度在1米以内。
5. 知识体系总览
下面这张图,是我梳理的坐标系与投影的核心逻辑。你看一遍,心里就有谱了。
这张图把整个知识体系串起来了。从上到下,先搞清楚地理坐标系和投影坐标系是什么,再理解转换原理,最后落到代码实现。我每次带新人,都先让他们看这张图。
做轨迹回放,坐标系是地基。地基没打好,后面全是坑。WGS84是国际标准,GCJ02是国内标准,Web墨卡托是展示标准。三者的转换,必须烂熟于心。
嗯,这一章就到这儿。坐标系这块儿,你只要把WGS84、GCJ02、墨卡托、UTM这四个概念吃透,再配合上面的代码,基本就能应对90%的轨迹回放场景了。