地心地固坐标系(ECEF)与站心坐标系(ENU)的转换

目录

1. 概述

2. 原理

2.1. 平移

2.2. 旋转

 2.3. 总结

3. 实现

4. 参考

1. 概述

      我在《大地经纬度坐标与地心地固坐标的的转换》这篇文章中已经论述了地心坐标系的概念。我们知道,基于地心坐标系的坐标都是很大的值,这样的值是不太方便进行空间计算的,所以很多时候可以选取一个站心点,将这个很大的值变换成一个较小的值。以图形学的观点来看,地心坐标可以看作是世界坐标,站心坐标可以看作局部坐标。

       站心坐标系以一个站心点为坐标原点,当把坐标系定义为X轴指东、Y轴指北,Z轴指天,就是ENU(东北天)站心坐标系。这样,从地心地固坐标系转换成的站心坐标系,就会成为一个符合常人对地理位置认知的局部坐标系。同时,只要站心点位置选的合理(通常可选取地理表达区域的中心点),表达的地理坐标都会是很小的值,非常便于空间计算。

注意站心天向(法向量)与赤道面相交不一定会经过球心 

2. 原理

       令选取的站心点为P,其大地经纬度坐标为 (Bp,Lp,Hp) (Bp,Lp,Hp),对应的地心地固坐标系为 (Xp,Yp,Zp) (Xp,Yp,Zp)。地心地固坐标系简称为ECEF,站心坐标系简称为ENU。

2.1. 平移

       通过第一节的图可以看出,ENU要转换到ECEF,一个很明显的图形操作是平移变换,将站心移动到地心。根据站心点P在地心坐标系下的坐标 (Xp,Yp,Zp) (Xp,Yp,Zp),可以很容易推出ENU转到ECEF的平移矩阵:

 反推之,ECEF转换到ENU的平移矩阵就是T的逆矩阵:

2.2. 旋转

另外一个需要进行的图形变换是旋转变换,其旋转变换矩阵根据P点所在的经度L和纬度B确定。这个旋转变换有点难以理解,需要一定的空间想象能力,但是可以直接给出如下结论:

  1. 当从ENU转换到ECEF时,需要先旋转再平移,旋转是先绕X轴旋转 (pi/2−B) (pi/2−B),再绕Z轴旋转 (pi/2+L) (pi/2+L)
  2. 当从ECEF转换到ENU时,需要先平移再旋转,旋转是先绕Z轴旋转 −(pi/2+L) −(pi/2+L),再绕X轴旋转 −(pi/2−B) −(pi/2−B)

根据我在《WebGL简易教程(五):图形变换(模型、视图、投影变换)》提到的旋转变换,绕X轴旋转矩阵为:

 绕Z轴旋转矩阵为:

从ENU转换到ECEF的旋转矩阵为:

根据三角函数公式:

sin(π/2+α)=cosαsin(π/2−α)=cosαcos(π/2+α)=−sinαcos(π/2−α)=sinα 有:

将(2)、(3)带入(1)中,则有:

 而从ECEF转换到ENU的旋转矩阵为:

 旋转矩阵是正交矩阵,根据正交矩阵的性质:正交矩阵的逆矩阵等于其转置矩阵,那么可直接得:

 2.3. 总结

将上述公式展开,可得从ENU转换到ECEF的图形变换矩阵为:

而从ECEF转换到ENU的图形变换矩阵为:

3. 实现

接下来用代码实现这个坐标转换,选取一个站心点,以这个站心点为原点,获取某个点在这个站心坐标系下的坐标:

#include <iostream>
#include <eigen3/Eigen/Eigen>

#include <osgEarth/GeoData>

using namespace std;

const double epsilon = ;
const double pi = ;
const double d2r = pi / ;
const double r2d =  / pi;

const double a = ;		//椭球长半轴
const double f_inverse = ;			//扁率倒数
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( - e * e * sin(B) * sin(B));
	x = (N + H) * cos(B) * cos(L);
	y = (N + H) * cos(B) * sin(L);
	z = (N * ( - e * e) + H) * sin(B);
}

void Xyz2Blh(double &x, double &y, double &z)
{
	double tmpX =  x;
	double temY = y ;
	double temZ = z;

	double curB = ;
	double N = ; 
	double calB = atan2(temZ, sqrt(tmpX * tmpX + temY * temY)); 
	
	int counter = ;
	while (abs(curB - calB) * r2d > epsilon  && counter < )
	{
		curB = calB;
		N = a / sqrt( - 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 * ( - 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 = ;
	double y = ;
	double z = ;

	//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 / );
	Eigen::AngleAxisd rzAngleAxis(rzAngle, Eigen::Vector3d(, , ));
	Eigen::Matrix3d rZ = rzAngleAxis.matrix();

	double rxAngle = -(pi /  - topocentricOrigin.y() * d2r);
	Eigen::AngleAxisd rxAngleAxis(rxAngle, Eigen::Vector3d(, , ));
	Eigen::Matrix3d rX = rxAngleAxis.matrix();

	Eigen::Matrix4d rotation;
	rotation.setIdentity();
	rotation.block<, >(, ) = (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(, ) = -tx;
	translation(, ) = -ty;
	translation(, ) = -tz;
	
	resultMat = rotation * translation;
}

void CalEnu2Ecef(Eigen::Vector3d& topocentricOrigin, Eigen::Matrix4d& resultMat)
{
	double rzAngle = (topocentricOrigin.x() * d2r + pi / );
	Eigen::AngleAxisd rzAngleAxis(rzAngle, Eigen::Vector3d(, , ));
	Eigen::Matrix3d rZ = rzAngleAxis.matrix();

	double rxAngle = (pi /  - topocentricOrigin.y() * d2r);
	Eigen::AngleAxisd rxAngleAxis(rxAngle, Eigen::Vector3d(, , ));
	Eigen::Matrix3d rX = rxAngleAxis.matrix();

	Eigen::Matrix4d rotation;
	rotation.setIdentity();
	rotation.block<, >(, ) = (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(, ) = tx;
	translation(, ) = ty;
	translation(, ) = tz;

	resultMat = translation * rotation;
}

void TestXYZ2ENU()
{
	double L = ;
	double B = ;
	double H = ;
	   	
	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 = ;
	double y = ;
	double z = ;
	Blh2Xyz(x, y, z);

	cout << "ECEF坐标(世界坐标):";
	Eigen::Vector4d xyz(x, y, z, );
	cout << xyz << endl;

	cout << "ENU坐标(局部坐标):";
	Eigen::Vector4d enu = wolrd2localMatrix * xyz;
	cout << enu << endl;	
}

void TestOE()
{
	double L = ;
	double B = ;
	double H = ;

	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 = ; i < ; i++)
	{
		for (int j = ; j < ; j++)
		{
			printf("%lf\t", worldToLocal.ptr()[j *  + i]);
		}
		cout << endl;
	}
	cout << endl;

	osg::Matrixd localToWorld;
	centerPoint.createLocalToWorld(localToWorld);

	cout << "站心转地心矩阵:" << endl;
	for (int i = ; i < ; i++)
	{
		for (int j = ; j < ; j++)
		{
			printf("%lf\t", localToWorld.ptr()[j *  + i]);
		}
		cout << endl;
	}
	cout << endl;

	double x = ;
	double y = ;
	double z = ;
	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. 参考

  1. 站心坐标系和WGS-84地心地固坐标系相互转换矩阵
  2. Transformations between ECEF and ENU coordinates
  3. GPS经纬度坐标WGS84到东北天坐标系ENU的转换
  4. 三维旋转矩阵;东北天坐标系(ENU);地心地固坐标系(ECEF);大地坐标系(Geodetic);经纬度对应圆弧距离

本文转自:地心地固坐标系(ECEF)与站心坐标系(ENU)的转换_亲手扳倒你 - 格物博客-PC万里 (pcwanli.com)

  • 1
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
心地ECEF坐标系与东北天(ENU坐标系是两种常用的地理坐标系,用于描述物体的位置和方向。它们之间可以进行坐标转换。 地心地坐标系是以地球中为原点,以地球自转轴为Z轴建立的坐标系。它的X轴指向经度为0度的子午线,Y轴指向经度为90度的子午线,Z轴垂直于地球表面向上。地心地坐标系可以直接通过地球球面坐标(经度、纬度、高度)与直角坐标系(X轴、Y轴、Z轴)进行转换。 东北天坐标系是与特定位置相关的本地坐标系,它的X轴指向东方,Y轴指向北方,Z轴垂直于地表向上。在东北天坐标系中,物体的位置和方向是相对于参考点而言的。 坐标系转换可以通过旋转、平移和缩放等变换来实现。具体做法是先将地心地坐标系中的点转换为地球上某一特定点的局部东北天坐标系中的点,再通过旋转将该点转换为目标位置的本地东北天坐标系中的点。 转换的具体步骤是先确定参考点的经纬度坐标以及在地心地坐标系中的坐标,然后计算出地心地坐标系中参考点到目标点的旋转角度和缩放比例,最后将地心地坐标系中的点进行旋转平移与缩放变换,得到目标位置的东北天坐标系中的点坐标。 通过地心地坐标系和东北天坐标系转换,我们可以方便地描述物体在空间中的位置和方向,应用于导航、航空、航天等领域。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值