C语言空间直角坐标与大地坐标的相互转换实验报告
源代码附文末或见Github:⬇️
直角坐标与大地坐标的相互转化branch:
- master: 正常读取输入。
- file_read: 从csv文件获取输入。
一、实验名称:空间直角坐标与大地坐标的相互转换
二、实验目的与要求
三、实验内容
-
已知某点的空间直角坐标(X、Y、Z), X = 1546823.34,Y = -3879765.13,Z = 4804185.05,椭球参数a= 6378137m,b= 6356752.3141m,采用下式将其转换为大地坐标(L、B)
-
根据1所求的大地坐标(L、B)及大地高H及相同的椭球参数将其转化为空间直角坐标(X、Y、Z),计算式为:
四、试验设备与环境
五、实验步骤
1 && 2 分析和算法设计
公理1 :由于 L B H LBH LBH 和 X Y Z XYZ XYZ 是相同物理量在相同参考系下的不同表示方法,因此在空间直角坐标系上的某点 ( X Y Z ) (XYZ) (XYZ),有且只有一个 L B H LBH LBH在大地坐标系与之对应。
推论1 只要 X Y Z XYZ XYZ确定, L B H LBH LBH的值就只与 X Y Z XYZ XYZ的取值有关,而与任何其他变量无关。
下面展开讨论:
由方程可知,虽然
L
L
L 可以直接求解,但求
B
B
B 的方程中,包含
B
B
B 本身,为了验证方程(2)是否可以求解,我们先将方程(2)化简:
B
=
arctan
Z
+
N
e
2
sin
B
X
2
+
Y
2
tan
B
=
Z
+
N
e
2
sin
B
X
2
+
Y
2
(2)
\begin{matrix} B = \arctan\dfrac{Z + Ne^2\sin B}{\sqrt{X^2 + Y^2}}\\\tan B = \dfrac{Z + Ne^2\sin B}{\sqrt{X^2 + Y^2}} \tag{2} \end{matrix}
B=arctanX2+Y2Z+Ne2sinBtanB=X2+Y2Z+Ne2sinB(2)
为了方便化简,我们令
X
2
+
Y
2
=
R
(3)
\sqrt{X^2 + Y^2} = R \tag{3}
X2+Y2=R(3)
又由于
N
=
a
1
−
e
2
sin
2
B
(4)
N = \frac{a}{\sqrt{1-e^2\sin^2B}} \tag{4}
N=1−e2sin2Ba(4)
将(4)、(3)带入(2)中
tan
B
=
Z
R
+
e
2
R
N
s
i
n
B
=
Z
R
+
e
2
R
sin
B
1
−
e
2
sin
2
B
=
Z
R
+
a
e
2
R
1
1
sin
2
B
−
e
2
(5)
\begin{matrix} \tan B &= \dfrac{Z}{R} + \dfrac{e^2}{R}NsinB\\&=\dfrac{Z}{R} + \dfrac{e^2}{R}\dfrac{\sin B}{\sqrt{1-e^2\sin^2B}} \\&= \dfrac{Z}{R} + \dfrac{ae^2}{R}\dfrac{1}{\sqrt{\dfrac{1}{\sin^2 B}-e^2}} \tag{5} \end{matrix}
tanB=RZ+Re2NsinB=RZ+Re21−e2sin2BsinB=RZ+Rae2sin2B1−e21(5)
由 推论1 可知,上式中
Z
R
\dfrac ZR
RZ 与
a
e
2
R
\dfrac{ae^2}{R}
Rae2 与
B
B
B 的值无关,为了方便表示,我们令
Z
R
=
C
,
a
e
2
R
=
K
.
\frac{Z}{R} = C, \quad \dfrac{ae^2}{R}=K.
RZ=C,Rae2=K.
上式化简为:
tan B = K 1 1 sin 2 B − e 2 + C (6*) \tan B = K\dfrac{1}{\sqrt{\dfrac{1}{\sin^2 B}-e^2}}+C \tag{6*} tanB=Ksin2B1−e21+C(6*)
在上式中,由于 tan B sin B = cos B \dfrac{\tan B}{\sin B} = \cos B sinBtanB=cosB 不为常量,故无法找到一组 k 1 , k 2 ∈ R k_1,k_2\in R k1,k2∈R 满足 k 1 tan B + k 2 sin B = 0 k_1\tan B + k_2 \sin B = 0 k1tanB+k2sinB=0。因此上式左右两端含有变量 B B B 的因子是非线性相关的,所以不可以直接通过方程求解。
由此,为得到 B B B 的值,必须通过其他方式求解。
利用计算机程序可以通过不断猜测 B B B 的值,带入(6)式中,如果(6)左右两端的值足够接近,且由 推论1, 此时猜测到的值 B 猜 B_猜 B猜 必然也会接近实际的值 B 实 B_实 B实。
我们将上述求 B B B 的方法称为遍历法,接下来我们设计遍历法的算法。
遍历法
由题设条件,可知 B ∈ ( − π 2 , π 2 ) B \in (-\dfrac{\pi}{2}, \dfrac{\pi}{2}) B∈(−2π,2π)。
- 一个可执行的算法必须由有限步构成。
但是可能出现的 B B B 的值确是无限的,为此,我们将 B B B 进行离散化 ,即每隔很小的值 δ ( d e l t a ) \delta (delta) δ(delta) ,取一个新的值 B B B。
为了方便表示,我们设计损失函数
L
o
s
s
Loss
Loss用来计算方程左右两端的差值。
L
o
s
s
(
B
)
=
tan
B
−
K
1
1
sin
2
B
−
e
2
+
C
(7)
Loss(B) = \tan B - K\dfrac{1}{\sqrt{\dfrac{1}{\sin^2 B}-e^2}}+C \tag{7}
Loss(B)=tanB−Ksin2B1−e21+C(7)
当函数Loss的值很小时,则认为此时的
B
B
B达到了目标值
B
实
B_实
B实。
用数学语言可以描述为:
若取一个足够小的 ε > 0 \varepsilon > 0 ε>0,
对于
B
0
=
−
π
2
+
k
δ
,
且
(
k
∈
N
∗
)
B
0
<
π
2
B_0 = -\frac{\pi}{2} + k\delta, 且(k\in N^*)B_0 < \dfrac{\pi}{2}
B0=−2π+kδ,且(k∈N∗)B0<2π
时,如果满足
∣
L
o
s
s
(
B
0
)
∣
<
ε
|Loss(B_0)| <\varepsilon
∣Loss(B0)∣<ε
则认为
B
=
B
0
B = B_0
B=B0。
梯度下降法
使用遍历法求解时,往往会由于需要遍历的变量的数量太多,导致程序的运行时间太长,难以得到正确的结果。当我们遍历时,只是盲目的遍历,我们并不知道到底经过多少个 δ \delta δ 后,能够使 L o s s Loss Loss函数的绝对值变小。这是因为,我们只用 L o s s Loss Loss 函数的值与0比较,这样的比较获得的信息量是十分少的,为了从 L o s s Loss Loss 函数中获取更多信息,我们可以考虑将 L o s s Loss Loss 函数的绝对值进行求导,将其导函数记为 L o s s ′ Loss' Loss′
我们的目标是使 L o s s Loss Loss 函数的值趋近于0,对于一个点 ( B , L o s s ( B ) ) (B, Loss(B)) (B,Loss(B)) ,如果 L o s s ( B ) > 0 Loss(B) > 0 Loss(B)>0,我们应该调整 B B B 的值,使得 L o s s ( B ) Loss(B) Loss(B)减少,相反,如果 L o s s ( B ) < 0 Loss(B) < 0 Loss(B)<0, 我们应该调整 B B B的值,使 L o s s ( B ) Loss(B) Loss(B)增加。
即:
- L o s s ( B ) > 0 Loss(B) > 0 Loss(B)>0, B B B 向 L o s s ′ ( B ) < 0 Loss'(B) < 0 Loss′(B)<0的方向移动。
- L o s s ( B ) < 0 , B Loss(B) < 0, B Loss(B)<0,B 向 L o s s ′ ( B ) > 0 Loss'(B) > 0 Loss′(B)>0 的方向移动。
以点 X = − 2758534 , Y = − 4132154 , Z = 3986124 X = -2758534, Y = -4132154, Z = 3986124 X=−2758534,Y=−4132154,Z=3986124为例,
绘制出 L o s s Loss Loss函数的图像:
发现 L o s s Loss Loss函数并不是简单的单调函数,用上述算法有可能达到一个使 L o s s ( B ) Loss(B) Loss(B)为0的点,也有可能达到函数的一个极值点,并不是在所有情况下都能符合条件。
二分法
虽然梯度下降法无法实现,但这给我们进一步实现程序提供了思路,即让 B B B 的取值变得更加具有方向性。
由于当
B
→
−
π
2
B\to -\dfrac{\pi}{2}
B→−2π 时,
L
o
s
s
(
B
)
→
−
∞
Loss(B) \to -\infin
Loss(B)→−∞
当
B
→
π
2
B\to \dfrac{\pi}{2}
B→2π时,
L
o
s
s
(
B
)
→
+
∞
Loss(B) \to +\infin
Loss(B)→+∞
又由推论2可知,在区间
(
−
π
2
,
π
2
)
(-\dfrac{\pi}{2}, \dfrac{\pi}{2})
(−2π,2π)内,
L
o
s
s
(
B
)
Loss(B)
Loss(B)有且只有一个一个零点。可以很容易想到二分法的求解方式:
- L o o p S t a r t : Loop Start: LoopStart:
- m i d = r i g h t + l e f t 2 mid = \dfrac{right +left}2 mid=2right+left
- i f L o s s ( m i d ) > ε , l e f t = m i d if \quad Loss(mid) > \varepsilon , left = mid ifLoss(mid)>ε,left=mid
- e l s e i f L o s s ( m i d ) < − ε , r i g h t = m i d else \quad if \quad Loss(mid) < -\varepsilon, right = mid elseifLoss(mid)<−ε,right=mid
- e l s e B = m i d else\quad B = mid elseB=mid
- : L o o p E n d :LoopEnd :LoopEnd
迭代法
由于式(6)
tan
B
=
K
1
1
sin
2
B
−
e
2
+
C
(6*)
\tan B = K\dfrac{1}{\sqrt{\dfrac{1}{\sin^2 B}-e^2}}+C \tag{6*}
tanB=Ksin2B1−e21+C(6*)
如果我们令方程左边的
B
=
B
n
+
1
B = B_{n+1}
B=Bn+1 方程右边的
B
=
B
n
B = B_n
B=Bn,就可以得到一个关于
B
B
B 的迭代方程。
B
n
+
1
=
arctan
(
K
1
1
sin
2
B
n
−
e
2
+
C
)
(8*)
B_{n+1} = \arctan\left(K\frac{1}{\sqrt{\dfrac{1}{\sin^2B_n}}-e^2}+C\right) \tag{8*}
Bn+1=arctan⎝⎜⎜⎛Ksin2Bn1−e21+C⎠⎟⎟⎞(8*)
我们称上面的递推式为函数
F
o
r
w
a
r
d
(
B
)
Forward(B)
Forward(B)。
以点 X = − 2758534 , Y = − 4132154 , Z = 3986124 X = -2758534, Y = -4132154, Z = 3986124 X=−2758534,Y=−4132154,Z=3986124为例,
绘制出 F o r w a r d Forward Forward函数的图像:
上图中粗线为 F o r w a r d Forward Forward函数,虚线为直线 B n + 1 = B n B_{n+1} = B_n Bn+1=Bn。
设曲线 F o r w a r d Forward Forward 与 直线 B n + 1 = B n B_{n+1} = B_n Bn+1=Bn的交点为 A A A , A A A的横坐标为 x 0 x_0 x0,很显然 A A A是函数 F o r w a r d Forward Forward 的一阶不动点。由递推函数的性质可知,
-
当 B n < x 0 B_n < x_0 Bn<x0 时, B n + 1 > B n B_{n+1} > B_n Bn+1>Bn
-
当 B n > x 0 B_n > x_0 Bn>x0时, B n + 1 < B n B_{n+1} < B_n Bn+1<Bn
-
当 B n = x 0 B_n = x_0 Bn=x0时, B n + 1 = B n B_{n+1} = B_n Bn+1=Bn
因此如果迭代次数无穷大,那么 B n B_n Bn的值会无限接近于 x 0 x_0 x0或等于 x 0 x_0 x0。
我们用 P y t h o n Python Python模拟一下迭代过程:
from math import * import matplotlib.pyplot as plt a = 6378137 b = 6356752 e = sqrt((a * a - b * b) / (a * a)) X = -2758534 Y = -4132154 Z = 3986124 R = sqrt(X * X + Y * Y) K = a * e * e / R C = Z / R x = [] y = [] def forWard(x): N = a / sqrt(1 - e * e * sin(B) * sin(B)) tmp = sqrt(1/(sin(x) * sin(x)) - e * e) return atan(K / tmp + 0.1) + C B = C times = 100 t = 0 while True: t += 1 nextB = forWard(B) #plt.plot(t, nextB, '+') B = nextB if t >= times: break x.append(t) y.append(B) plt.xlabel("times") plt.ylabel("B") plt.plot(x, y) plt.show()
理论和实践都告诉我们,使用迭代公式计算时, B B B 会趋近于某一个特殊的值 x 0 x_0 x0。
下证:该 x 0 x_0 x0就是我们的目标值 B 实 B_实 B实
当
B
=
x
0
B = x_0
B=x0时,由于
x
0
x_0
x0是
F
o
r
w
a
r
d
Forward
Forward 函数的一阶不动点,故有
tan
B
=
K
1
1
sin
2
B
−
e
2
+
C
\tan B = K\dfrac{1}{\sqrt{\dfrac{1}{\sin^2 B}-e^2}}+C
tanB=Ksin2B1−e21+C
将上式带入
L
o
s
s
Loss
Loss函数中,可以得到
L
o
s
s
(
B
)
=
0
Loss(B) = 0
Loss(B)=0,又由推论1知这样的
B
B
B是唯一的,因此
B
=
x
0
B = x_0
B=x0 就是所求值。
证毕。
(上述证明其实显而易见,用迭代法的关键是证明一阶不动点存在, 但是该证明过程较为复杂,我们可以在物理领域内使用反证法:如果不存在一阶不动点,那么函数 L o s s Loss Loss就没有解,这与实际情况不符,故一定存在一阶不动点)。
第二小问直接带入公式计算。
3流程图
遍历法:
二分法:
迭代法:
4、 程序代码
本部分只使用迭代法完成代码。
//
// main.cpp
// ChangePosition
//
// Created by Sumbrella on 2020/4/28.
// Copyright © 2020 Sumbrella All rights reserved.
//
# include <stdio.h>
# include <math.h>
typedef long long ll;
const ll a = 6378137;
const ll b = 6356752;
const double epslion = 1e-15;
double e, R, K, C;
// 配置需要使用的中间参数。
void configure(double X, double Y, double Z)
{
e = sqrt((a * a - b * b) / (a * a));
R = sqrt(X * X + Y * Y);
K = a * e * e / R;
C = Z / R;
}
// 迭代法迭代一次
double Forward(double B)
{
double tmp = sqrt(1 / (sin(B) * sin (B)) - e * e );
return atan(K / tmp + C);
}
void inputXYZ(double *X,double *Y, double *Z)
{
printf("请输入X,Y,Z的值>>>(空格隔开)");
scanf("%lf %lf %lf", X, Y, Z);
}
void getLBH(double X, double Y, double Z, double *pN, double *pL, double *pB, double *pH)
{
double B = C; // 让B的初始值为C
while (1)
{
double nB = Forward(B);
if (nB - B < epslion && nB - B > -epslion)
break;
B = nB;
}
double N = a / sqrt(1 - e * e * sin(B) * sin(B));
double L = atan2(Y, X);
double H = R / cos(B) - N;
*pN = N; *pL = L; *pB = B; *pH = H;
}
void showLBH(double L, double B, double H)
{
printf("转换得到的大地坐标:\n");
printf("L = %.3f, B = %.3f, H = %.3f\n", L, B, H);
}
void showXYZ(double N, double L, double B, double H)
{
double X = (N + H) * cos(B) * cos(L);
double Y = (N + H) * cos(B) * sin(L);
double Z = (N * (1 - e * e) + H) * sin(B);
printf("转换后得到的空间坐标:\n");
// printf("N = %.6f", N);
printf("X = %.6f, Y = %.6f, Z = %.6f\n", X, Y, Z);
}
int main()
{
double X, Y, Z;
double N, L, B, H; // 定义变量
inputXYZ(&X, &Y, &Z); // 获取XYZ的值
configure(X, Y, Z); // 配置参数
getLBH(X, Y, Z, &N, &L, &B, &H); // 计算LBH的值
showLBH(L, B, H); // 输出LBH的值
showXYZ(N, L, B, H); // 输出计算得到的XYZ的值
return 0;
}
5、 主要函数和变量说明
主要函数说明见上图。
变量和常量说明:
(X,Y,Z)、(L,B,H)分别对应直角坐标和大地坐标下的坐标值。
C = Z / R, K = ae^2 / R。
6、 程序运行结果
转换后得到的坐标与原始数据完全一致,认为程序通过。