1. 前言

前面的文章中系统的阐述了工程坐标的转换类别和转换的方法。关于转换代码实现,有很多的类库:

这里针对GPS接收的WGS84椭球的经纬度转换为地方坐标系的问题,利用C#,对工程坐标转换方法和步骤做出详细的解答。不基于任何类库和函数库,也未使用矩阵库,可以便利的将代码移植到任何语言。


2. 计算总体框架

根据上一篇文章中对七参数、四参数、高程拟合在坐标转换的作用和使用条件的阐述,我们可以将上一篇文章第7节的总结图,按照计算的流程重新绘制。

根据上图可知,预将WGS84椭球的GPS坐标需要经过5次转换。其中,

  1. 转换1、转换3在charlee44的博客:大地经纬度坐标与地心地固坐标的转换中详细讲解了,并且有C++代码的实现,利用C#重构即可。
  2. 转换2、转换5,以及他们的组合,在我的上一篇文章(工程)坐标转换类别和方法也详细的讲解了。

因此,根据计算原理,直接可以利用C#代码实现。


3. C#代码实现

3.1 整体类的构建

5个转换是对点的操作,不妨构建自定义点类MyPoint,在这个类中定义转换方法。在实现转换方法之前,需要定义数据属性,以承载转换参数和转换数据。代码框架如下:

internal class MyPoint
{
// 定义椭球类型。这里仅列举了4中国内常见的椭球类型
// 国际椭球可以增加自行定义
public enum EllipsoidType
{
WGS84,
CGCS2000,
西安80,
北京54
}
//大地坐标经度、维度、高度
public double L { get; set; }
public double B { get; set; }
public double H { get; set; } //空间坐标系
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; } //七参数转换后的空间坐标
public double X2 { get; set; }
public double Y2 { get; set; }
public double Z2 { get; set; } private double a = 0, f = 0, b = 0, e = 0, e2 = 0; //椭球参数 private readonly double rho = 180 / Math.PI;
private readonly double d2r = Math.PI / 180; public double Xs { get; set; }
public double Ys { get; set; }
public double Hs { get; set; } //七参数 三个线性平移量-单位米 三个旋转平移量-十进制秒为单位(运算时注意转换为度) 比例因子-单位百万分率 (ppm)
//测量队给出的七参数单位与计算的单位不同,要进行单位转化 1 秒=0.0000048481373323 弧度
//尺度因子有两种单位的表示形式,一种结果约为1,如1.0000045,用k表示;
//另一种就是ppm的表示形式,稍微比1大一点,如4.5,用m表示。k=m/1000000
private double dx = 0, dy = 0, dz = 0, rx = 0, ry = 0, rz = 0, m = 0, k = 0;
}

3.2 椭球参数赋值

常见的椭球参数值在我的文章经纬度坐标转换为工程坐标可以找到,这里选取与上述代码对应的4类椭球,并在上述MyPoint类中增加函数EllipsoidParam(EllipsoidType type)

/// <summary>
/// 椭球参数设置
/// </summary>
/// <param name="type">椭球类型</param>
private void EllipsoidParam(EllipsoidType type)
{
// CGCS2000 椭球参数
if (type == EllipsoidType.CGCS2000)
{
this.a = 6378137;
this.f = 1 / 298.257222101;
} // 西安 80
else if (type == EllipsoidType.西安80)
{
this.a = 6378140;
this.f = 1 / 298.257;
} // 北京 54
else if (type == EllipsoidType.北京54)
{
this.a = 6378245;
this.f = 1 / 298.3;
} // WGS-84
else
{
this.a = 6378137;
this.f = 1 / 298.257223563;
} this.b = this.a * (1 - this.f);
this.e = Math.Sqrt(this.a * this.a - this.b * this.b) / this.a; //第一偏心率
this.e2 = Math.Sqrt(this.a * this.a - this.b * this.b) / this.b; //第二偏心率
}

3.3 转换1、3(大地经纬度坐标与地心地固坐标的转换)

charlee44的博客有C++代码的实现,现在利用C#重构即可。上述MyPoint类中增加BLH2XYZ(EllipsoidType type)XYZ2BLH(EllipsoidType type)两个函数

/// <summary>
/// 经纬度坐标转空间直角坐标
/// </summary>
/// <param name="type">椭球类型</param>
public void BLH2XYZ(EllipsoidType type = EllipsoidType.WGS84)
{
EllipsoidParam(type); double sB = Math.Sin(this.B * d2r);
double cB = Math.Cos(this.B * d2r);
double sL = Math.Sin(this.L * d2r);
double cL = Math.Cos(this.L * d2r);
double N = this.a / (Math.Sqrt(1 - this.e * this.e * sB * sB)); this.X = (N + this.H) * cB * cL;
this.Y = (N + this.H) * cB * sL;
this.Z = (N * (1 - this.e * this.e) + this.H) * sB; this.X2 = this.X;
this.Y2 = this.Y;
this.Z2 = this.Z;
} /// <summary>
/// 空间直角坐标转经纬度坐标
/// </summary>
/// <param name="type">椭球类型</param>
public void XYZ2BLH(EllipsoidType type)
{
EllipsoidParam(type); // 这里转出来的B L是弧度
this.L = Math.Atan(this.Y2 / this.X2) + Math.PI; this.L = this.L * 180 / Math.PI;
// B需要迭代计算
double B2 = Math.Atan(Z2 / Math.Sqrt(X2 * X2 + Y2 * Y2));
double B1;
double N;
while (true)
{
N = a / Math.Sqrt(1 - f * (2 - f) * Math.Sin(B2) * Math.Sin(B2));
B1 = Math.Atan((Z2 + N * f * (2 - f) * Math.Sin(B2)) / Math.Sqrt(X2 * X2 + Y2 * Y2));
if (Math.Abs(B1 - B2) < 1e-12)
break;
B2 = B1;
}
this.B = B2 * 180 / Math.PI;
double sB = Math.Sin(this.B * d2r);
double cB = Math.Cos(this.B * d2r);
this.H = this.Z2 / sB - N * (1 - this.e * this.e);
}

3.4 投影转换

此处仅实现了常见的高斯-克里格投影。上述MyPoint类中增加GaussProjection(EllipsoidType type, ProjectionSetting prjSetting)函数

/// <summary>
/// 利用高斯投影将指定椭球类型的经纬度坐标转为投影坐标
/// </summary>
/// <param name="type">椭球类型</param>
/// <param name="prjSetting">投影设置实例</param>
public void GaussProjection(EllipsoidType type, ProjectionSetting prjSetting)
{
this.EllipsoidParam(type); double l = (this.L - prjSetting.CenterL) / this.rho; double cB = Math.Cos(this.B * this.d2r);
double sB = Math.Sin(this.B * this.d2r);
double s2b = Math.Sin(this.B * this.d2r * 2);
double s4b = Math.Sin(this.B * this.d2r * 4);
double s6b = Math.Sin(this.B * this.d2r * 6);
double s8b = Math.Sin(this.B * this.d2r * 8); double N = this.a / Math.Sqrt(1 - this.e * this.e * sB * sB); // 卯酉圈曲率半径
double t = Math.Tan(this.B * this.d2r);
double eta = this.e2 * cB; double m0 = this.a * (1 - this.e * this.e);
double m2 = 3.0 / 2.0 * this.e * this.e * m0;
double m4 = 5.0 / 4.0 * this.e * this.e * m2;
double m6 = 7.0 / 6.0 * this.e * this.e * m4;
double m8 = 9.0 / 8.0 * this.e * this.e * m6; double a0 = m0 + 1.0 / 2.0 * m2 + 3.0 / 8.0 * m4 + 5.0 / 16.0 * m6 + 35.0 / 128.0 * m8;
double a2 = 1.0 / 2.0 * m2 + 1.0 / 2.0 * m4 + 15.0 / 32.0 * m6 + 7.0 / 16.0 * m8;
double a4 = 1.0 / 8.0 * m4 + 3.0 / 16.0 * m6 + 7.0 / 32.0 * m8;
double a6 = 1.0 / 32.0 * m6 + 1.0 / 16.0 * m8;
double a8 = 1.0 / 128.0 * m8; // X1为自赤道量起的子午线弧长
double X1 = a0 * (this.B * this.d2r) - 1.0 / 2.0 * a2 * s2b + 1.0 / 4.0 * a4 * s4b - 1.0 / 6.0 * a6 * s6b + 1.0 / 8.0 * a8 * s8b; this.Xs = X1 + N / 2 * t * cB * cB * l * l + N / 24 * t * (5 - t * t + 9 * Math.Pow(eta, 2) + 4 * Math.Pow(eta, 4)) * Math.Pow(cB, 4) * Math.Pow(l, 4)
+ N / 720 * t * (61 - 58 * t * t + Math.Pow(t, 4)) * Math.Pow(cB, 6) * Math.Pow(l, 6); this.Ys = N * cB * l + N / 6 * (1 - t * t + eta * eta) * Math.Pow(cB, 3) * Math.Pow(l, 3)
+ N / 120 * (5 - 18 * t * t + Math.Pow(t, 4) + 14 * Math.Pow(eta, 2) - 58 * eta * eta * t * t) * Math.Pow(cB, 5) * Math.Pow(l, 5); this.Hs = this.H; // 假东 假北偏移 this.Xs += prjSetting.PseudoNorth;
this.Ys += prjSetting.PseudoEast;
}

其中,ProjectionSetting是一个投影参数设置类,独立于MyPoint,用于设定中央经线、东偏等投影参数

internal class ProjectionSetting
{
private double _centerL; public double CenterL
{
get { return _centerL; }
set { _centerL = value; }
} private double _centerB; public double CenterB
{
get { return _centerB; }
set { _centerB = value; }
} private double _pseudoEast; public double PseudoEast
{
get { return _pseudoEast; }
set { _pseudoEast = value; }
} private double _pseudoNorth; public double PseudoNorth
{
get { return _pseudoNorth; }
set { _pseudoNorth = value; }
} private double _prjScale; public double PrjScale
{
get { return _prjScale; }
set { _prjScale = value; }
} /// <summary>
/// 设置全部的投影参数
/// </summary>
/// <param name="centerL"></param>
/// <param name="centerB"></param>
/// <param name="pseudoEast"></param>
/// <param name="pseudoNorth"></param>
/// <param name="prjScale"></param>
public ProjectionSetting(double centerL, double centerB,
double pseudoEast, double pseudoNorth,
double prjScale)
{
CenterL = centerL;
CenterB = centerB;
PseudoEast = pseudoEast;
PseudoNorth = pseudoNorth;
PrjScale = prjScale;
} /// <summary>
/// 仅设置中央经线和东偏
/// </summary>
/// <param name="centerL"></param>
/// <param name="pseudoEast"></param>
public ProjectionSetting(double centerL, double pseudoEast)
{
CenterL = centerL;
CenterB = 0.0;
PseudoEast = pseudoEast;
PseudoNorth = 0.0;
PrjScale = 1.0;
} /// <summary>
/// 默认常用投影参数,中央经线120°,东偏500000
/// </summary>
public ProjectionSetting()
{
CenterL = 120.0;
CenterB = 0.0;
PseudoEast = 500000;
PseudoNorth = 0.0;
PrjScale = 1.0;
}
}

3.5 转换2的实现(三参数、七参数)

上述MyPoint类中增加SevenParamTrans(Datum7Paras datum7Paras)TreeParamTrans(Datum3Paras datum3Paras)函数

/// <summary>
/// 利用7参数进行坐标系之间转换
/// </summary>
/// <param name="datum7Paras">7参数实例</param>
public void SevenParamTrans(Datum7Paras datum7Paras)
{
this.dx = datum7Paras.Dx;
this.dy = datum7Paras.Dy;
this.dz = datum7Paras.Dz;
this.rx = datum7Paras.Rx * 0.0000048481373323; //1 秒=0.0000048481373323 弧度
this.ry = datum7Paras.Ry * 0.0000048481373323;
this.rz = datum7Paras.Rz * 0.0000048481373323;
this.m = datum7Paras.PPM;
this.k = this.m / 1000000; this.X2 = (1 + k) * (this.X + this.rz * this.Y - this.ry * this.Z) + this.dx;
this.Y2 = (1 + k) * (-this.rz * this.X + this.Y + this.rx * this.Z) + this.dy;
this.Z2 = (1 + k) * (this.ry * this.X - this.rx * this.Y + this.Z) + this.dz;
} /// <summary>
/// 利用3参数进行坐标系之间转换
/// </summary>
/// <param name="datum3Paras">3参数实例</param>
public void TreeParamTrans(Datum3Paras datum3Paras)
{
this.dx = datum3Paras.Dx;
this.dy = datum3Paras.Dy;
this.dz = datum3Paras.Dz; this.X2 = this.X + this.dx;
this.Y2 = this.Y + this.dy;
this.Z2 = this.Z + this.dz;
}

Datum3ParasDatum7Paras独立于MyPoint,用于设定坐标转换参数

   /// <summary>
/// 7参数
/// </summary>
internal class Datum7Paras
{
private double _dx; public double Dx
{
get { return _dx; }
set { _dx = value; }
} private double _dy; public double Dy
{
get { return _dy; }
set { _dy = value; }
} private double _dz; public double Dz
{
get { return _dz; }
set { _dz = value; }
} private double _rx; public double Rx
{
get { return _rx; }
set { _rx = value; }
} private double _ry; public double Ry
{
get { return _ry; }
set { _ry = value; }
} private double _rz; public double Rz
{
get { return _rz; }
set { _rz = value; }
} private double _ppm; public double PPM
{
get { return _ppm; }
set { _ppm = value; }
} public Datum7Paras(double dx, double dy, double dz,
double rx, double ry, double rz,
double ppm)
{
_dx = dx;
_dy = dy;
_dz = dz; _rx = rx;
_ry = ry;
_rz = rz; _ppm = ppm;
}
}
    internal class Datum3Paras
{
private double _dx; public double Dx
{
get { return _dx; }
set { _dx = value; }
} private double _dy; public double Dy
{
get { return _dy; }
set { _dy = value; }
} private double _dz; public double Dz
{
get { return _dz; }
set { _dz = value; }
} public Datum3Paras(double dx, double dy, double dz)
{
Dx = dx;
Dy = dy;
Dz = dz;
}
}

3.6 转换5的实现(四参数+高程拟合)

上述MyPoint类中增加Transform4Para(Trans4Paras transPara)函数。此处,高程拟合仅实现了已知一个测点的固定改正差

/// <summary>
/// 投影坐标获取后,进一步利用4参数转换坐标
/// </summary>
/// <param name="transPara"></param>
public void Transform4Para(Trans4Paras transPara)
{
var X1 = transPara.Dx;
var Y1 = transPara.Dy; var cosAngle = Math.Cos(transPara.A);
var sinAngle = Math.Sin(transPara.A); X1 += transPara.K * (cosAngle * this.Xs - sinAngle * this.Ys);
Y1 += transPara.K * (sinAngle * this.Xs + cosAngle * this.Ys); this.Xs = X1;
this.Ys = Y1;
// 固定改正差
this.Hs += transPara.Dh;
}

Trans4Paras独立于MyPoint,用于设定坐标转换参数

    internal class Trans4Paras
{
private double _dx; public double Dx
{
get { return _dx; }
set { _dx = value; }
} private double _dy; public double Dy
{
get { return _dy; }
set { _dy = value; }
} private double _a; public double A
{
get { return _a; }
set { _a = value; }
} private double _k; public double K
{
get { return _k; }
set { _k = value; }
} private double _dh; public double Dh
{
get { return _dh; }
set { _dh = value; }
} public Trans4Paras(double dx, double dy, double a, double k, double dh)
{
Dx = dx;
Dy = dy;
A = a;
K = k;
Dh = dh;
} public Trans4Paras()
{
}
}

3.7 调用过程

里面的参数,因为保密原因,做出了随机更改,实际使用时可根据自己情况赋值。

3.7.1 一步法

// 实例化计算参数
MyPoint p = new MyPoint();. p.L=113.256;
p.B=31.565;
p.H=5.216; // 经纬度转空间坐标
p.BLH2XYZ(); // 实例化七参数
Datum7Paras datum7Paras = new Datum7Paras(
489.2994563566, 141.1525159753, 15.74421120568,
-0.164423, 4.141573, -4.808299,
-6.56482989958); p.SevenParamTrans(datum7Paras); // 空间坐标转回经纬度
p.XYZ2BLH(EllipsoidType.WGS84); // 高斯投影 经纬度转平面坐标
// 实例化投影参数类
ProjectionSetting projectionSetting = new ProjectionSetting(120,500000);
p.GaussProjection(EllipsoidType.WGS84, projectionSetting);

3.7.2 两步法

// 实例化计算参数
MyPoint p = new MyPoint();. p.SetLBH(113.256,31.565,5.216); // 经纬度转空间坐标
p.BLH2XYZ(); // 实例化七参数
Datum7Paras datum7Paras = new Datum7Paras(
489.2994563566, 141.1525159753, 15.74421120568,
-0.164423, 4.141573, -4.808299,
-6.56482989958); p.SevenParamTrans(datum7Paras); // 空间坐标转回经纬度
p.XYZ2BLH(EllipsoidType.WGS84); // 高斯投影 经纬度转平面坐标
// 实例化投影参数类
ProjectionSetting projectionSetting = new ProjectionSetting(120,500000);
p.GaussProjection(EllipsoidType.WGS84, projectionSetting); Trans4Paras transformPara = new(6456.15957352521, -134618.390707439, 0.011104964500129, 1.00002537583871, 5.788); p.Transform4Para(transformPara);

4. 总结

至此,关于工程坐标系转化,即GPS接收的WGS84椭球的经纬度转换为地方坐标系的问题,基本全部实现。代码正确性和准确性的验证是与 南方GPS工具箱做对比。例如,采用上述的一步法,在设定好坐标、7参数、投影参数后,计算发现,与南方GPS工具箱在y方向偏差1mm。结果如下图:

工程坐标转换方法C#代码实现的更多相关文章

  1. 百度地图 iOS SDK - 坐标转换方法

    百度地图 Android SDK 要么 iOS SDK 或各种 API 工具产品,我们使用百度自己的加密坐标系. 员在使用过程中,位置点都是通过 GPS 或者其它途径获取的.所以与百度地图所使用的坐标 ...

  2. 使用Eclipse可以方便的统计工程或文件的代码行数,

    使用Eclipse可以方便的统计工程或文件的代码行数,方法如下: 1.点击要统计的项目或许文件夹,在菜单栏点击Search,然后点击File... 2.选中正则表达式(Regular expressi ...

  3. react第十八单元(redux中间件redux-thunk,redux工程目录的样板代码,规范目录结构)

    第十八单元(redux中间件redux-thunk,redux工程目录的样板代码,规范目录结构) #课程目标 中间件:中间件增强redux的可扩展性,实现功能复用的目的. redux-thunk异步逻 ...

  4. Android Studio删除工程里面无用的代码和资源

    如果你是一个经常开发android应用程序或者做android维护项目的人,我想说你对我谈论的这个话题,一定会感兴趣的. 因为只有做到了这两点,你的项目生成的apk包才会更小,而不是随着你的开发和维护 ...

  5. .NET Core 多项目工程生成EF迁移代码

    错误表现 dotnet ef的官方文档针对的是单个项目的情况,即启动项目就是DbContext所在项目. 对于分层结构的解决方案如启动项目是WebApi项目,DbContext在基础设施项目,在Web ...

  6. VisualStudio2010项目转换为VisualStudio2005项目:解决方案和工程项目文件转换方法(2)

    因为我现在不喜欢把一篇博客写的很长很长,这篇博客是接着上一篇博客来写的.上一篇文章我很详细的说明了修改项目文件解决方案的过程.这篇文章我就说说项目中的项目文件该怎么修改.因为我平日里主要做的是ASP. ...

  7. Chisel3-创建工程并转换为Verilog代码

    https://mp.weixin.qq.com/s/ie0R3v60IcrI6beTXHrgSg   基于Intellj IDEA+Scala插件模式开发   因为Chisel内嵌于Scala,所以 ...

  8. maven工程的如何进行代码调试

    1.maven项目的父项目右键选择:maven build    注意:       1.选择Browser workspace,让BaseDirectory变成:${***}形式.       2. ...

  9. 实际工程中加快 Java 代码编写的小提示

    这里我简单谈谈 Java 语法在编程效率方面的弱势,以及如何补救. 一.集合的快速创建 C# 是少数拥有集合字面值(又叫初始化表达式)的静态语言之一. var list = new List<i ...

  10. (五)stm32工程代码HardFault异常查错调试方法

    一.导致异常的原因很多,例如:直接使用未分配空间的指针.栈溢出等一场非法操作便会使程序进入HardFault异常状态.下面介绍怎么找出程序中的异常. 接下来在keil_MDK工程中,编译代码,并deb ...

随机推荐

  1. 文档的CURD

    Index方式,需要指定ID 如果ID不存在,创建新的文档,状态是created 如果ID存在,会先删除现有ID的文档,然后创建一个新文档,ID数加1,状态是updated PUT test/_doc ...

  2. 使用css制作轮播图

    <!--HTML代码--> <!DOCTYPE html> <html lang="en"><head> <meta char ...

  3. prettierrc格式化常用配置

    #最大长度 printWidth: 140 #单引号 singleQuote: true tabWidth: 2 useTabs: false # 句尾添加分号 semi: false # js an ...

  4. PAT (Basic Level) Practice 1009 说反话 分数 20

    给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出. 输入格式: 测试输入包含一个测试用例,在一行内给出总长度不超过 80 的字符串.字符串由若干单词和若干空格组成,其中单词是由英文字母(大小 ...

  5. 企业使用erp系统的好处及解决了什么问题?

    不是所有的企业使用ERP都能带来好处的,尤其是对于一些小微企业,带来的可能是灾难,而实施不适用的系统同样也会带来意想不到的后果,所以在ERP的使用方面得根据自己企业实际做决定.不同规模的企业选用不同的 ...

  6. 图片 css剪切,等比例缩放

    object-fit: cover; .img1 {//自定义图片宽高,并且等比例缩放 width: 200px; height: 400px; object-fit: cover; }

  7. POJ3260 The Fewest Coins(混合背包)

    支付对应的是多重背包问题,找零对应完全背包问题. 难点在于找上限T+maxv*maxv,可以用鸽笼原理证明,实在想不到就开一个尽量大的数组. 1 #include <map> 2 #inc ...

  8. Hive 自定义UDF操作步骤

    Hive 自定义UDF操作步骤 需要自定义类,然后继承UDF 然后在方法envluate()方法里面实现具体的业务逻辑,打包上传到linux(以免出错打包成RunningJar) 一.创建临时函数 ( ...

  9. 网页头部的声明应该是用 lang="zh" 还是 lang="zh-CN"?

    网页头部的声明应该是用 lang="zh" 还是 lang="zh-CN"? 遇到问题 不知道大家有没有留意到一个问题,就是使用 VsCode 新建的 html ...

  10. numpy(ndarray)和tensor(GPU上的numpy)速查

    类型(Types) Numpy PyTorch np.ndarray torch.Tensor np.float32 torch.float32; torch.float np.float64 tor ...