源代码附文末或见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 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)增加。
即:
以点 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)有且只有一个一个零点。可以很容易想到二分法的求解方式:
由于式(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就没有解,这与实际情况不符,故一定存在一阶不动点)。
第二小问直接带入公式计算。
本部分只使用迭代法完成代码。
//
// main.cpp
// ChangePosition
//
// Created by Sumbrella on 2020/4/28.
// Copyright © 2020 Sumbrella All rights reserved.
//
# include
# include
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;
}
主要函数说明见上图。
变量和常量说明:
(X,Y,Z)、(L,B,H)分别对应直角坐标和大地坐标下的坐标值。
C = Z / R, K = ae^2 / R。