我的通用横轴墨卡托投影学习笔记。
UTM Hello World
- 通用横轴墨卡托(Universal Transverse Mercator,UTM)投影是一种地图投影方法,将经纬度转换为二维笛卡尔坐标表示;
- UTM坐标是一种水平位置表示方式,忽略了海拔高度,将地球建模为完美椭球体;
-
UTM坐标格式:
[经度区间][纬度区间][方格坐标]
- 经度区间:从西经180°开始,每6°一个区间,自西向东按照从0到60排列;
- 在挪威西海岸以及斯瓦尔巴群岛附近的区域有例外;
- 纬度区间:从南纬80°开始,每8°一个区间,自南向北按照从C到X排列;
- 覆盖南纬80°到北纬84°N之间的区域;
- 为避免和数字1和0混淆,跳过I和O;
- 方格坐标:使用东向坐标(easting)和北向坐标(northing)表示,原点是中央经线和赤道的交点;
- 为避免东向坐标出现负值,将原点西移500千米,东向坐标取值范围为0-10000千米;
- 在北半球,北向坐标取值范围为0-9300千米;
- 在南半球,北向坐标取值范围为10000-11000千米;
- 经度区间:从西经180°开始,每6°一个区间,自西向东按照从0到60排列;
- UTM投影在中央经线处的尺度因子为0.9996,保证每个分区内的尺度因子均在1附近,在使用时,可以认为是等尺度的,误差不超过千分之一;
- 经度小数点后六位精确到米,纬度受到实际纬度影响,在中国可以认为经纬度取到小数点后六位大约在米级精度;
-
主要城市UTM分区:
- 50N:北京、广州;
- 49N:深圳;
算法实现
使用hobuinc/mgrs工程中utm.h
文件中定义的转换函数可以实现经纬度和UTM投影之间的相互转换:
原型声明:
long Convert_Geodetic_To_UTM(double Latitude,
double Longitude,
long* Zone,
char* Hemisphere,
double* Easting,
double* Northing);
long Convert_UTM_To_Geodetic(long Zone,
char Hemisphere,
double Easting,
double Northing,
double* Latitude,
double* Longitude);
返回值含义:
#define UTM_NO_ERROR 0x0000
#define UTM_LAT_ERROR 0x0001
#define UTM_LON_ERROR 0x0002
#define UTM_EASTING_ERROR 0x0004
#define UTM_NORTHING_ERROR 0x0008
#define UTM_ZONE_ERROR 0x0010
#define UTM_HEMISPHERE_ERROR 0x0020
#define UTM_ZONE_OVERRIDE_ERROR 0x0040
#define UTM_A_ERROR 0x0080
#define UTM_INV_F_ERROR 0x0100
收敛角
收敛角(Convergence Angle,CA)指的是UTM投影中某一点处的网格北(Grid North)与真北(True North)之间的夹角。
#include <cmath>
static constexpr double kDEG2RAD = 0.017453292519943; // deg -> rad
static constexpr double kRAD2DEG = 57.295779513082323; // rad -> deg
/**
* @brief 计算给定经纬度位置处的收敛角
* @note 中央子午线采用六度带规定
* @param lon_deg 经度,单位为度,东经为正,西经为负
* @param lat_deg 纬度,单位为度,北纬为正,南纬为负
* @return double 收敛角,单位为度,网格北转向真北,逆时针为正
*/
inline double GetConvergenceAngle(double lon_deg, double lat_deg) {
if (lon_deg < -180 || lon_deg > 180) {
LOG(ERROR) << "Invalid longitude: " << std::to_string(lon_deg) << " deg.";
return 0.0;
}
if (lat_deg < -90 || lat_deg > 90) {
LOG(ERROR) << "Invalid latitude: " << std::to_string(lat_deg) << " deg.";
return 0.0;
}
int N = std::floor(std::abs(lon_deg) / 6);
double central_meridian = std::copysign(6 * (N + 1) - 3, lon_deg);
// *精确公式
double convergence_angle =
std::atan(std::tan((lon_deg - central_meridian) * kDEG2RAD) * std::sin(lat_deg * kDEG2RAD)) * kRAD2DEG;
// *近似公式,仅在距离中央子午线较近的时候成立
// double convergence_angle = (std::sin(lat_deg * kDEG2RAD)) * (lon_deg - central_meridian);
return convergence_angle;
}
参考
- Universal Transverse Mercator coordinate system-Wikipedia
- Transverse Mercator projection-Wikipedia
- utm投影中央经线长度比为什么是0.9996?-知乎用户的回答-知乎
- Calculating grid convergence (True North to Grid North)-Stack Exchange
- 备用-UTM投影分带查询表-大男孩99的文章-知乎
- 十进制经纬度坐标小数点后几位能精确到米?-RoverTang的回答-知乎
- hobuinc/mgrs
- UTM ↔ WGS84 conversion in C++
- UTM投影分带相关资料及计算公式-CSDN博客
- UTM投影与高斯克吕格投影中分带带号与中央经线经度的计算关系-CSDN博客
- 根据经纬度算UTM带号-博客园