WGS84: World Geodetic System 1984,世界大地测量系统,1984 版为当前最新版,被用于 GPS(Global Positioning System,全球定位系统)
UTM: Universal Transverse Mercator coordinate system,通用横轴墨卡托投影坐标系
Longitude - 经度,Latitude - 纬度,Easting - 东距,Northing - 北距
写在前面
本文要解决的是如何将定位方案(例如组合导航)返回的经纬度信息转换为用 XY 表示的平面坐标,从而更加直观地描述位置与位置变化,且保持较高的精度。
基础知识
WGS84 经纬度
地球不是正球体,而是椭球体,WGS 即用于描述这个椭球体,其主要参数只有两个:赤道半径(equatorial radius)和极半径(polar radius)。1960-1984 经过 WGS60、WGS66、WGS72 和 WGS84 四个版本,精度逐步提升,其中 WGS84 精度最高,一直沿用至今。
下图为 WGS84 的赤道半径 a、极半径 b 和平均地球半径 (2a+b)/3:
WGS 对经纬度的使用是不可或缺的,因为经纬度是一种球面坐标系(如下图),其需要大地基准(geodetic datum)从而将球面位置映射到椭球面上,WGS84 就是一种广泛使用的大地基准。
墨卡托投影
杰拉杜斯·墨卡托(Gerardus Mercator)是 16 世纪西欧地理学家,墨卡托投影是他为了绘制世界地图而提出的。为什么会提到墨卡托?因为将经纬度转换为平面坐标和绘制世界地图是同类任务(地图是平面的)。
将球 / 椭球展成平面是困难的,最大的问题在于长度、面积和角度上的变形。墨卡托投影都是圆柱投影,即将椭球体投影到圆柱体上,然后将圆柱体展成平面。下图展示了三种墨卡托投影,从上至下为标准墨卡托、斜轴墨卡托和横轴墨卡托。标准墨卡托延最长的纬线——赤道线展开,横轴墨卡托延经线展开。
下图中左右侧分别为使用标准墨卡托投影和横轴墨卡托投影绘制的世界地图。标准墨卡托投影中,远离赤道的部分形变巨大,格陵兰岛几乎和非洲一样大,而实际上格陵兰岛的面积是非洲的 1/14。横轴墨卡托投影中,远离中央经线(沿着哪条经线展开,哪条经线就是中央经线)的区域有较大形变,由于图中格陵兰岛和非洲都接近中央经线,两者的面积关系是基本准确的。
既然中央经线附近的扭曲较小,那是否可以隔一定经度按横轴墨卡托展开一次?答案是肯定的,这也是下一节——通用横轴墨卡托的核心思想。
UTM 通用横轴墨卡托
UTM 将地球分为 60 个投影带(如下图),去除了 84°N 以北和 80°S 以南的极地区域,每个投影带跨越 6° 经度,共 360°,投影带内长度变形小于 0.1%。UTM 适合局部的投影定位,但不适合世界地图的绘制,因为其总是在中央经线形变小、在投影带交界处形变大,使世界地图产生不均匀的形变。
这个网站能查看自己位于哪个投影带 link(网页内的世界地图使用标准墨卡托投影绘制),下图为笔者的位置,位于北半球中央经线为 117° 的投影带,编号 50。
UTM 使用东距和北距来表示投影带内的位置,相当于 X 和 Y。经过人为设置,东距和北距在任何时候都是正值:
- 对于北半球
- 东距的原点(东距为 0 的点)为中央经线以西 500 000 m,这导致包含在投影面内的赤道线位于东距 166 000 m 到 834 000 m 之间。
- 北距的原点(北距为 0 的点)为赤道线,84°N 最北点的北距为 9 300 000 m。
- 对于南半球
- 东距同北半球
- 北距的最大值为 10 000 000 m,向南逐渐减少,80°S 最南点的北距为 1 100 000 m。
下图为一个 UTM 投影面及其平面展开的示意图。
C/C++ 实现经纬度转 UTM
使用开源项目 utm,Github 链接 link,百度网盘链接 link(提取码 wsaa)。需要用到其中的 datum.h
、datum.c
、utm.h
和 utm.c
。
#include "utm.h"
#include "datum.h"
// 输入:纬度
double latitude = 39.95584802654687;
// 输入:经度
double longitude = 116.31607926526445;
// 输出:北距
double N;
// 输出:东距
double E;
// 输出:投影面编号
GridZone zone = GRID_AUTO;
// 输出:北半球或南半球
Hemisphere hemi = HEMI_AUTO;
// 大地基准设置
const Ellipse* e = standard_ellipse(ELLIPSE_WGS84);
// 进行转换
geographic_to_grid(e->a, e->e2, latitude, longitude, &zone, &hemi, &N, &E);
除了 WGS84,datum.cpp
中还预设了包括 WGS72 在内的多种大地基准:
static StandardEllipse reference_ellipse[] = {
{ "Airy 1830", "AA", 6377563.396, ECC2(299.3249646) },
{ "Australian National", "AN", 6378160, ECC2(298.25) },
{ "Bessel 1841, Ethiopia, Indonesia, Japan and Korea",
"BR", 6377397.155, ECC2(299.1528128) },
{ "Bessel 1841, Namibia", "BN", 6377483.865, ECC2(299.1528128) },
{ "Clarke 1866", "CC", 6378206.4, ECC2(294.9786982) },
{ "Clarke 1880", "CD", 6378249.145, ECC2(293.465) },
{ "Everest, Brunei and E. Malaysia (Sabah and Sarawak)",
"EB", 6377298.556, ECC2(300.8017) },
{ "Everest, India 1830", "EA", 6377276.345, ECC2(300.8017) },
{ "Everest, India 1956", "EC", 6377301.243, ECC2(300.8017) },
{ "Everest, Pakistan", "EF", 6377309.613, ECC2(300.8017) },
{ "Everest, W. Malaysia and Singapore 1948",
"EE", 6377304.063, ECC2(300.8017) },
{ "Everest, W. Malaysia 1969",
"ED", 6377295.664, ECC2(300.8017) },
{ "Geodetic Reference System 1980",
"RF", 6378137, ECC2(298.257222101) },
{ "Helmert 1906", "HE", 6378200, ECC2(298.3) },
{ "Hough 1960", "HO", 6378270, ECC2(297) },
{ "Indonesian 1974", "ID", 6378160, ECC2(298.247) },
{ "International 1924", "IN", 6378388, ECC2(297) },
{ "Krassovsky 1940", "KA", 6378245, ECC2(298.3) },
{ "Modified Airy", "AM", 6377340.189, ECC2(299.3249646) },
{ "Modified Fischer 1960", "FA", 6378155, ECC2(298.3) },
{ "South American 1969", "SA", 6378160, ECC2(298.25) },
{ "WGS 1972", "WD", 6378135, ECC2(298.26) },
{ "WGS 1984", "WE", 6378137, ECC2(298.257223563) }
};