地心地固坐标系(ECEF)与站心坐标系(ENU)的转换
1. 概述
我在《大地经纬度坐标与地心地固坐标的的转换》这篇文章中已经论述了地心坐标系的概念。我们知道,基于地心坐标系的坐标都是很大的值,这样的值是不太方便进行空间计算的,所以很多时候可以选取一个站心点,将这个很大的值变换成一个较小的值。以图形学的观点来看,地心坐标可以看作是世界坐标,站心坐标可以看作局部坐标。
站心坐标系以一个站心点为坐标原点,当把坐标系定义为X轴指东、Y轴指北,Z轴指天,就是ENU(东北天)站心坐标系。这样,从地心地固坐标系转换成的站心坐标系,就会成为一个符合常人对地理位置认知的局部坐标系。同时,只要站心点位置选的合理(通常可选取地理表达区域的中心点),表达的地理坐标都会是很小的值,非常便于空间计算。
2. 原理
令选取的站心点为P,其大地经纬度坐标为\((B_p,L_p,H_p)\),对应的地心地固坐标系为\((X_p,Y_p,Z_p)\)。地心地固坐标系简称为ECEF,站心坐标系简称为ENU。
2.1. 平移
通过第一节的图可以看出,ENU要转换到ECEF,一个很明显的图形操作是平移变换,将站心移动到地心。根据站心点P在地心坐标系下的坐标\((X_p,Y_p,Z_p)\),可以很容易推出ENU转到ECEF的平移矩阵:
\begin{bmatrix}
1&0&0&X_p\\
0&1&0&Y_p\\
0&0&1&Z_p\\
0&0&0&1\\
\end{bmatrix}
\]
反推之,ECEF转换到ENU的平移矩阵就是T的逆矩阵:
\begin{bmatrix}
1&0&0&-X_p\\
0&1&0&-Y_p\\
0&0&1&-Z_p\\
0&0&0&1\\
\end{bmatrix}
\]
2.2. 旋转
另外一个需要进行的图形变换是旋转变换,其旋转变换矩阵根据P点所在的经度L和纬度B确定。这个旋转变换有点难以理解,需要一定的空间想象能力,但是可以直接给出如下结论:
- 当从ENU转换到ECEF时,需要先旋转再平移,旋转是先绕X轴旋转\((\frac{pi}{2}-B)\),再绕Z轴旋转\((\frac{pi}{2}+L)\)
- 当从ECEF转换到ENU时,需要先平移再旋转,旋转是先绕Z轴旋转\(-(\frac{pi}{2}+L)\),再绕X轴旋转\(-(\frac{pi}{2}-B)\)
根据我在《WebGL简易教程(五):图形变换(模型、视图、投影变换)》提到的旋转变换,绕X轴旋转矩阵为:
\begin{bmatrix}
1&0&0&0\\
0&cosθ&-sinθ&0\\
0&sinθ&cosθ&0\\
0&0&0&1\\
\end{bmatrix}
\]
绕Z轴旋转矩阵为:
\begin{bmatrix}
cosθ&-sinθ&0&0\\
sinθ&cosθ&0&0\\
0&0&1&0\\
0&0&0&1\\
\end{bmatrix}
\]
从ENU转换到ECEF的旋转矩阵为:
\tag{1}
\]
根据三角函数公式:
sin(π/2-α)=cosα\\
cos(π/2+α)=-sinα\\
cos(π/2-α)=sinα\\
\]
有:
\begin{bmatrix}
-sinL&-cosL&0&0\\
cosL&-sinL&0&0\\
0&0&1&0\\
0&0&0&1\\
\end{bmatrix}
\tag{2}
\]
\begin{bmatrix}
1&0&0&0\\
0&sinB&-cosB&0\\
0&cosB&sinB&0\\
0&0&0&1\\
\end{bmatrix}
\tag{3}
\]
将(2)、(3)带入(1)中,则有:
\begin{bmatrix}
-sinL&-sinBcosL&cosBcosL&0\\
cosL&-sinBsinL&cosBsinL&0\\
0&cosB&sinB&0\\
0&0&0&1\\
\end{bmatrix}
\tag{4}
\]
而从ECEF转换到ENU的旋转矩阵为:
\tag{5}
\]
旋转矩阵是正交矩阵,根据正交矩阵的性质:正交矩阵的逆矩阵等于其转置矩阵,那么可直接得:
\begin{bmatrix}
-sinL&cosL&0&0\\
-sinBcosL&-sinBsinL&cosB&0\\
cosBcosL&cosBsinL&sinB&0\\
0&0&0&1\\
\end{bmatrix}
\tag{6}
\]
2.3. 总结
将上述公式展开,可得从ENU转换到ECEF的图形变换矩阵为:
\begin{bmatrix}
1&0&0&X_p\\
0&1&0&Y_p\\
0&0&1&Z_p\\
0&0&0&1\\
\end{bmatrix}
\begin{bmatrix}
-sinL&-sinBcosL&cosBcosL&0\\
cosL&-sinBsinL&cosBsinL&0\\
0&cosB&sinB&0\\
0&0&0&1\\
\end{bmatrix}
\]
而从ECEF转换到ENU的图形变换矩阵为:
\begin{bmatrix}
-sinL&cosL&0&0\\
-sinBcosL&-sinBsinL&cosB&0\\
cosBcosL&cosBsinL&sinB&0\\
0&0&0&1\\
\end{bmatrix}
\begin{bmatrix}
1&0&0&-X_p\\
0&1&0&-Y_p\\
0&0&1&-Z_p\\
0&0&0&1\\
\end{bmatrix}
\]
3. 实现
接下来用代码实现这个坐标转换,选取一个站心点,以这个站心点为原点,获取某个点在这个站心坐标系下的坐标:
#include <iostream>
#include <eigen3/Eigen/Eigen>
#include <osgEarth/GeoData>
using namespace std;
const double epsilon = 0.000000000000001;
const double pi = 3.14159265358979323846;
const double d2r = pi / 180;
const double r2d = 180 / pi;
const double a = 6378137.0; //椭球长半轴
const double f_inverse = 298.257223563; //扁率倒数
const double b = a - a / f_inverse;
//const double b = 6356752.314245; //椭球短半轴
const double e = sqrt(a * a - b * b) / a;
void Blh2Xyz(double &x, double &y, double &z)
{
double L = x * d2r;
double B = y * d2r;
double H = z;
double N = a / sqrt(1 - e * e * sin(B) * sin(B));
x = (N + H) * cos(B) * cos(L);
y = (N + H) * cos(B) * sin(L);
z = (N * (1 - e * e) + H) * sin(B);
}
void Xyz2Blh(double &x, double &y, double &z)
{
double tmpX = x;
double temY = y ;
double temZ = z;
double curB = 0;
double N = 0;
double calB = atan2(temZ, sqrt(tmpX * tmpX + temY * temY));
int counter = 0;
while (abs(curB - calB) * r2d > epsilon && counter < 25)
{
curB = calB;
N = a / sqrt(1 - e * e * sin(curB) * sin(curB));
calB = atan2(temZ + N * e * e * sin(curB), sqrt(tmpX * tmpX + temY * temY));
counter++;
}
x = atan2(temY, tmpX) * r2d;
y = curB * r2d;
z = temZ / sin(curB) - N * (1 - e * e);
}
void TestBLH2XYZ()
{
//double x = 113.6;
//double y = 38.8;
//double z = 100;
//
//printf("原大地经纬度坐标:%.10lf\t%.10lf\t%.10lf\n", x, y, z);
//Blh2Xyz(x, y, z);
//printf("地心地固直角坐标:%.10lf\t%.10lf\t%.10lf\n", x, y, z);
//Xyz2Blh(x, y, z);
//printf("转回大地经纬度坐标:%.10lf\t%.10lf\t%.10lf\n", x, y, z);
double x = -2318400.6045575836;
double y = 4562004.801366804;
double z = 3794303.054150639;
//116.9395751953 36.7399177551
printf("地心地固直角坐标:%.10lf\t%.10lf\t%.10lf\n", x, y, z);
Xyz2Blh(x, y, z);
printf("转回大地经纬度坐标:%.10lf\t%.10lf\t%.10lf\n", x, y, z);
}
void CalEcef2Enu(Eigen::Vector3d& topocentricOrigin, Eigen::Matrix4d& resultMat)
{
double rzAngle = -(topocentricOrigin.x() * d2r + pi / 2);
Eigen::AngleAxisd rzAngleAxis(rzAngle, Eigen::Vector3d(0, 0, 1));
Eigen::Matrix3d rZ = rzAngleAxis.matrix();
double rxAngle = -(pi / 2 - topocentricOrigin.y() * d2r);
Eigen::AngleAxisd rxAngleAxis(rxAngle, Eigen::Vector3d(1, 0, 0));
Eigen::Matrix3d rX = rxAngleAxis.matrix();
Eigen::Matrix4d rotation;
rotation.setIdentity();
rotation.block<3, 3>(0, 0) = (rX * rZ);
//cout << rotation << endl;
double tx = topocentricOrigin.x();
double ty = topocentricOrigin.y();
double tz = topocentricOrigin.z();
Blh2Xyz(tx, ty, tz);
Eigen::Matrix4d translation;
translation.setIdentity();
translation(0, 3) = -tx;
translation(1, 3) = -ty;
translation(2, 3) = -tz;
resultMat = rotation * translation;
}
void CalEnu2Ecef(Eigen::Vector3d& topocentricOrigin, Eigen::Matrix4d& resultMat)
{
double rzAngle = (topocentricOrigin.x() * d2r + pi / 2);
Eigen::AngleAxisd rzAngleAxis(rzAngle, Eigen::Vector3d(0, 0, 1));
Eigen::Matrix3d rZ = rzAngleAxis.matrix();
double rxAngle = (pi / 2 - topocentricOrigin.y() * d2r);
Eigen::AngleAxisd rxAngleAxis(rxAngle, Eigen::Vector3d(1, 0, 0));
Eigen::Matrix3d rX = rxAngleAxis.matrix();
Eigen::Matrix4d rotation;
rotation.setIdentity();
rotation.block<3, 3>(0, 0) = (rZ * rX);
//cout << rotation << endl;
double tx = topocentricOrigin.x();
double ty = topocentricOrigin.y();
double tz = topocentricOrigin.z();
Blh2Xyz(tx, ty, tz);
Eigen::Matrix4d translation;
translation.setIdentity();
translation(0, 3) = tx;
translation(1, 3) = ty;
translation(2, 3) = tz;
resultMat = translation * rotation;
}
void TestXYZ2ENU()
{
double L = 116.9395751953;
double B = 36.7399177551;
double H = 0;
cout << fixed << endl;
Eigen::Vector3d topocentricOrigin(L, B, H);
Eigen::Matrix4d wolrd2localMatrix;
CalEcef2Enu(topocentricOrigin, wolrd2localMatrix);
cout << "地心转站心矩阵:" << endl;
cout << wolrd2localMatrix << endl<<endl;
cout << "站心转地心矩阵:" << endl;
Eigen::Matrix4d local2WolrdMatrix;
CalEnu2Ecef(topocentricOrigin, local2WolrdMatrix);
cout << local2WolrdMatrix << endl;
double x = 117;
double y = 37;
double z = 10.3;
Blh2Xyz(x, y, z);
cout << "ECEF坐标(世界坐标):";
Eigen::Vector4d xyz(x, y, z, 1);
cout << xyz << endl;
cout << "ENU坐标(局部坐标):";
Eigen::Vector4d enu = wolrd2localMatrix * xyz;
cout << enu << endl;
}
void TestOE()
{
double L = 116.9395751953;
double B = 36.7399177551;
double H = 0;
osgEarth::SpatialReference *spatialReference = osgEarth::SpatialReference::create("epsg:4326");
osgEarth::GeoPoint centerPoint(spatialReference, L, B, H);
osg::Matrixd worldToLocal;
centerPoint.createWorldToLocal(worldToLocal);
cout << fixed << endl;
cout << "地心转站心矩阵:" << endl;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%lf\t", worldToLocal.ptr()[j * 4 + i]);
}
cout << endl;
}
cout << endl;
osg::Matrixd localToWorld;
centerPoint.createLocalToWorld(localToWorld);
cout << "站心转地心矩阵:" << endl;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%lf\t", localToWorld.ptr()[j * 4 + i]);
}
cout << endl;
}
cout << endl;
double x = 117;
double y = 37;
double z = 10.3;
osgEarth::GeoPoint geoPoint(spatialReference, x, y, z);
cout << "ECEF坐标(世界坐标):";
osg::Vec3d out_world;
geoPoint.toWorld(out_world);
cout << out_world.x() <<'\t'<< out_world.y() << '\t' << out_world.z() << endl;
cout << "ENU坐标(局部坐标):";
osg::Vec3d localCoord = worldToLocal.preMult(out_world);
cout << localCoord.x() << '\t' << localCoord.y() << '\t' << localCoord.z() << endl;
}
int main()
{
//TestBLH2XYZ();
cout << "使用Eigen进行转换实现:" << endl;
TestXYZ2ENU();
cout <<"---------------------------------------"<< endl;
cout << "通过OsgEarth进行验证:" << endl;
TestOE();
}
这个示例先用Eigen矩阵库,计算了坐标转换需要的矩阵和转换结果;然后通过osgEarth进行了验证,两者的结果基本一致。运行结果如下:
4. 参考
- 站心坐标系和WGS-84地心地固坐标系相互转换矩阵
- Transformations between ECEF and ENU coordinates
- GPS经纬度坐标WGS84到东北天坐标系ENU的转换
- 三维旋转矩阵;东北天坐标系(ENU);地心地固坐标系(ECEF);大地坐标系(Geodetic);经纬度对应圆弧距离
地心地固坐标系(ECEF)与站心坐标系(ENU)的转换的更多相关文章
- GNSS学习笔记--坐标转换
GNSS 坐标转换 GNSS计算主要涉及三个坐标系,地心地固坐标系,地理坐标系和站心坐标系.这里主要介绍一下三个坐标的含义和转换公式. 地心地固坐标系如图X,Y,Z表示 (ECEF坐标系),以地心O为 ...
- RTKLIB源码解析(一)——单点定位(pntpos.c)
RTKLIB源码解析(一)--单点定位(pntpos.c) 标签: GNSS RTKLIB 单点定位 [TOC] pntpos int pntpos (const obsd_t *obs, int n ...
- 利用Hackrf One进行GPS定位欺骗制作超级跑马机
0×00 驾校的困惑 现行规定要求每个学员都必须在驾校练习够规定的学时,才能参加考试,在每台教练车上都安装有计时计程终端,学员刷卡刷指纹后开始累计里程.但是目前中国的很多驾校,存在车少人多的情况,假设 ...
- ECEF和大地坐标系的相互转化
在阅读 RTKLIB的源码时,发现了ECEF和大地坐标系的相互转换的函数,大地坐标系(φ,λ,h)转成ECEF(X,Y,Z)与所看书籍(GPS原理与接收机,谢刚,电子工业出版社)的公式是一样的,而EC ...
- ArcGIS中的坐标系定义与转换 (转载)
原文:ArcGIS中的坐标系定义与转换 (转载) 1.基准面概念: GIS中的坐标系定义由基准面和地图投影两组参数确定,而基准面的定义则由特定椭球体及其对应的转换参数确定,因此欲正确定义GIS系统坐 ...
- fbx 模型转换 export
最近在做自定义类型到fbx的转换 有关polygon的理解 vertex,normal,color等信息,是离散的放置的,对fbx里面的mesh加了控制点(vertex)信息之后, 需要再设置poly ...
- PROJ.4学习——坐标系转换
PROJ.4学习——坐标系转换 前言 PROJ可以做任从最简单的投影到许多参考数据非常复杂的转换.PROJ最初是作为地图投影工具开发的,但随着时间的推移,它已经发展成为一个强大的通用坐标转换引擎,可以 ...
- GIS中的坐标系定义与转换
GIS中的坐标系定义与转换 青岛海洋地质研究所 戴勤奋 2002-3-27 14:22:47 ----------------------------------------------------- ...
- Arcgis:坐标系统极其转换
1. ArcGIS中的坐标系统 ArcGIS中预定义了两套坐标系统,地理坐标系(Geographic coordinate system)和投影坐标系(Projectedcoordinate syst ...
随机推荐
- 将svn项目导出,再导入到其他工作空间
方法一: 对于一致svn地址,本地没有的项目,直接eclipse中svn检出即可. 若本地有项目,但想导入到另一个工作空间(即拷贝一份,不想再从svn拉),则需要用export方法. 方法二(expo ...
- spring-data-redis 动态切换数据源
最近遇到了一个麻烦的需求,我们需要一个微服务应用同时访问两个不同的 Redis 集群.一般我们不会这么使用 Redis,但是这两个 Redis 本来是不同业务集群,现在需要一个微服务同时访问. 其实我 ...
- ES6——静态属性与静态方法
静态方法只能写在class内,constructor外.通过static关键字声明 静态属性只能写在class外,通过 类名.属性名 = 属性值 声明 //静态属性与静态方法(ES6明确规定,Clas ...
- promise的信任问题&控制反转
//信任问题 //第三方的某个库 function method(cb){ setTimeout(function(){ cb && cb(); //这个库的bug:函数被多调用了一次 ...
- SpringMVC笔记(2)
一.SpringMVC的数据响应 1.1 数据响应方式 1.1.1 页面跳转 直接返回字符串 将返回的字符串与内部资源视图解析器的前后缀拼接 进行访问(默认为转发) 通过ModelAndView对象返 ...
- Linux - last 命令(Mac 电脑)
前言 为啥写这篇? 因为听 grep.sed 教程的时候有这个命令栗子 加上工作中,运维给我排查问题的时候也用到了,感觉挺重要,先了解为敬! 命令作用 显示用户和TTY的最后登录次数 这个是在 Mac ...
- openswan协商流程之(六):main_inI3_outR3()
主模式第六包:main_inI3_outR3 1. 序言 main_inI3_outR3()函数是ISAKMP协商过程中第六包的核心处理函数的入口,第五六包主要用来验证对方的身份信息,同时此报文也是加 ...
- Go并发编程--正确使用goroutine
目录 1. 对创建的gorouting负载 1.1 不要创建一个你不知道何时退出的 goroutine 1.1.1 不要帮别人做选择 1.1.2 不要作为一个旁观者 1.1.3 不要创建不知道什么时候 ...
- 【第二篇】- Git 安装配置之Spring Cloud直播商城 b2b2c电子商务技术总结
Git 安装配置 在使用Git前我们需要先安装 Git.Git 目前支持 Linux/Unix.Solaris.Mac和 Windows 平台上运行. Git 各平台安装包下载地址为:http://g ...
- OpenGL渲染管道,Shader,VAO&VBO&EBO
OpenGL渲染管线 (也就是)OpenGL渲染一帧图形的流程 以下列举最简单的,渲染一个三角形的流程,你可以将它视为 精简版OpenGL渲染管线 更复杂的流程也仅仅就是:在此基础上的各个流程中 添加 ...