小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
0、写在前面
经纬度是经度与纬度的合称组成一个坐标系统。又称为地理坐标系统,它是一种利用三度空间的球面来定义地球上的空间的球面坐标系统,能够标示地球上的任何一个位置。
纬线:纬线和经线一样是人类为度量方便而假设出来的辅助线,定义为地球表面某点随地球自转所形成的轨迹。任何一根纬线都是圆形而且两两平行。纬线的长度是赤道的周长乘以纬线的纬度的余弦,所以赤道最长,离赤道越远的纬线,周长越短,到了两极就缩为0。纬线指示东西方向。从赤道向北和向南,各分90°,称为北纬和南纬,分别用“N”和“S”表示。
经线:经线也称子午线,和纬线一样是人类为度量方便而假设出来的辅助线,定义为地球表面连接南北两极的大圆线上的半圆弧。任两根经线的长度相等,相交于南北两极点。每一根经线都有其相对应的数值,称为经度。经线指示南北方向。
经度:东经正数,西经为负数。经度是地球上一个地点离一根被称为本初子午线的南北方向走线以东或以西的度数。
纬度:北纬为正数,南纬为负数。纬度 是指某点与地球球心的连线和地球赤道面所成的线面角,其数值在0至90度之间。位于赤道以北的点的纬度叫北纬,记为N;位于赤道以南的点的纬度称南纬,记为S。
在一些Unity的三维仿真的项目中,我们有时候会遇到关于Unity世界坐标转地球经纬度的需求,在其他的网站上也有一些码友发表关于一些Unity世界坐标转地球经纬度算法的案例,但是有些大多都不支持商用,误差比较大,首先我们从根本出发,去了解一下经纬度算法的理论知识,结合理论知识,我们根据公式编写出的代码就会大大的减小误差。
1、先用Word画了一个草图
2、分析
由此可见,O是球心,O′是理论北极点,A是纬度和经度零点,C是同纬度上经度的任意一点,点B在⌒O′C上,OB是球半径,⌒AC为一段所在赤道上的弧线,B点为目标经纬度坐标,B′是B点在平面xy上z轴负方向上的投影,B″则是B点在平面xz上的投影,OC是O′C在平面xy上z轴负方向的投影,则B′在OC上,所以∠AOB′为经度,∠B′OB为纬度。
标记:Lon = ∠AOB′,Lat = ∠B′OB, R = OB,B点坐标为B(X, Y, Z)
3、经纬度转空间坐标
我们知道不同经度或者不同纬度的每一度对应的实际距离都是不同的,所以我们在计算的时候要通过傅立叶余弦级数计算之后的纬度函数获得。
而这些参数是根据e2的椭圆函数对于WGS84球体给出的
m1 = 111132.95255
m2 = -559.84957
m3 = 1.17514
m4 = -0.00230
p1 = 111412.87733
p2 = -93.50412
p3 = 0.11774
p4 = -0.000165
注:p4过于太小,可忽略不计。 接下来将常量代入到傅里叶余弦级数中,表达式如下:
4、翻译成C#代码
则,代码表示为
/// <summary>
/// 计算度长度
/// </summary>
/// <param name="lat"></param>
private void FindMetersPerLat(double lat)
{
double m1 = 111132.92d; // 纬度计算术语1
double m2 = -559.82d; // 纬度计算术语2
double m3 = 1.175d; // 纬度计算术语3
double m4 = -0.0023d; // 纬度计算术语4
double p1 = 111412.84d; // 经度计算术语1
double p2 = -93.5d; // 经度度计算术语2
double p3 = 0.118d; // 经度度计算术语3
lat *= Mathf.Deg2Rad;
// 计算一个经纬度的长度,以米为单位
metersPerLat = m1 + (m2 * Convert.ToDouble(Mathf.Cos(2 * (float)lat))) + (m3 * Convert.ToDouble(Mathf.Cos(4 * (float)lat))) + (m4 * Convert.ToDouble(Mathf.Cos(6 * (float)lat)));
metersPerLon = (p1 * Convert.ToDouble(Mathf.Cos((float)lat))) + (p2 * Convert.ToDouble(Mathf.Cos(3 * (float)lat))) + (p3 * Convert.ToDouble(Mathf.Cos(5 * (float)lat)));
}
世界坐标转经纬度:
private DoubleVector2 ConvertUCStoGPS(DoubleVector3 position)
{
FindMetersPerLat(_LatOrigin);
DoubleVector2 geoLocation = new DoubleVector2(0, 0);
geoLocation.x = (_LatOrigin + (position.z) / metersPerLat); //计算当前纬度
geoLocation.y = (_LonOrigin + (position.x) / metersPerLon); //计算当前经度
return geoLocation;
}
经纬度转世界坐标:
private DoubleVector3 ConvertGPStoUCS(DoubleVector2 gps)
{
FindMetersPerLat(_LatOrigin);
double zPosition = metersPerLat * (gps.x - _LatOrigin); //计算当前纬度
double xPosition = metersPerLon * (gps.y - _LonOrigin); //计算当前经度
return new DoubleVector3(xPosition, 0, zPosition);
}
注意:DoubleVector3和DoubleVector2两个结构体是我单独封装的Double类型的Vector,和Vector2,Vector3相同,只是这里必须要用双精度浮点数据需要,这里的代码就不写了,后边可以下载本程序Demo。
另外这里写一个对Vector3的扩展float Vector3转DoubleVector3:
internal static class VectorUtil
{
public static DoubleVector3 ToDoubleVector3(this Vector3 vector3)
{
double x = Convert.ToDouble(vector3.x);
double y = Convert.ToDouble(vector3.y);
double z = Convert.ToDouble(vector3.z);
return new DoubleVector3(x, y, z);
}
}
Tips:在本工程中有一个SetLocalOrigin方法,在默认情况下,你所有的转换都会假设你使用a (0,0)的经纬度起源。这让你在现实生活中的起源位于非洲新几内亚湾外的地球经纬度。当在使用默认的(0,0),你所有的gps转换将被定位在现实生活中。 但是,有时你只希望将GPS坐标转换为本地区域位于UCS(0,0,0)内部的统一。为了集中在一个当地的地区 在起源。你需要找到与你的场景的起源。如果你将该坐标传递给该函数。所有的 其余会话的转换将与新的原点有关。
链接:pan.baidu.com/s/19vYKmS1b… 提取码:mf5b